# frozen_string_literal: true

RSpec.describe TagsController do
  fab!(:user)
  fab!(:admin)
  fab!(:regular_user, :trust_level_4)
  fab!(:moderator)
  fab!(:category)
  fab!(:subcategory) { Fabricate(:category, parent_category_id: category.id) }

  before { SiteSetting.tagging_enabled = true }

  describe "#index" do
    fab!(:test_tag) { Fabricate(:tag, name: "test", description: "some description") }

    fab!(:topic_tag) do
      Fabricate(
        :tag,
        name: "topic-test",
        public_topic_count: 1,
        staff_topic_count: 1,
        pm_topic_count: 5,
      )
    end

    fab!(:pm_only_tag) do
      Fabricate(:tag, public_topic_count: 0, staff_topic_count: 0, pm_topic_count: 1)
    end

    fab!(:synonym) { Fabricate(:tag, name: "synonym", target_tag: topic_tag) }

    shared_examples "retrieves the right tags" do
      it "retrieves all tags as a staff user" do
        sign_in(admin)

        get "/tags.json"

        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]

        serialized_tag = tags.find { |t| t["name"] == test_tag.name }

        expect(serialized_tag["count"]).to eq(0)
        expect(serialized_tag["pm_count"]).to eq(nil)
        expect(serialized_tag["pm_only"]).to eq(false)

        serialized_tag = tags.find { |t| t["name"] == topic_tag.name }

        expect(serialized_tag["count"]).to eq(1)
        expect(serialized_tag["pm_count"]).to eq(nil)
        expect(serialized_tag["pm_only"]).to eq(false)
      end

      it "does not include pm_count attribute when user cannot tag PM topics even if display_personal_messages_tag_counts site setting has been enabled" do
        SiteSetting.display_personal_messages_tag_counts = true

        sign_in(admin)

        get "/tags.json"

        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]

        expect(tags[0]["name"]).to eq(test_tag.name)
        expect(tags[0]["pm_count"]).to eq(nil)

        expect(tags[1]["name"]).to eq(topic_tag.name)
        expect(tags[1]["pm_count"]).to eq(nil)
      end

      it "includes pm_count attribute when user can tag PM topics and display_personal_messages_tag_counts site setting has been enabled" do
        SiteSetting.display_personal_messages_tag_counts = true
        SiteSetting.pm_tags_allowed_for_groups = Group::AUTO_GROUPS[:admins]

        sign_in(admin)

        get "/tags.json"

        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]

        serialized_tag = tags.find { |t| t["name"] == test_tag.name }

        expect(serialized_tag["pm_count"]).to eq(0)
        expect(serialized_tag["pm_only"]).to eq(false)

        serialized_tag = tags.find { |t| t["name"] == topic_tag.name }

        expect(serialized_tag["pm_count"]).to eq(5)
        expect(serialized_tag["pm_only"]).to eq(false)

        serialized_tag = tags.find { |t| t["name"] == pm_only_tag.name }

        expect(serialized_tag["pm_count"]).to eq(1)
        expect(serialized_tag["pm_only"]).to eq(true)
      end

      it "only retrieve tags that have been used in public topics for non-staff user" do
        sign_in(user)

        get "/tags.json"

        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]
        expect(tags.length).to eq(1)

        expect(tags[0]["name"]).to eq(topic_tag.name)
        expect(tags[0]["count"]).to eq(1)
        expect(tags[0]["pm_count"]).to eq(nil)
      end
    end

    context "with pm_tags_allowed_for_groups" do
      fab!(:admin)
      fab!(:topic) { Fabricate(:topic, tags: [topic_tag]) }
      fab!(:pm) do
        Fabricate(
          :private_message_topic,
          tags: [test_tag],
          topic_allowed_users: [Fabricate.build(:topic_allowed_user, user: admin)],
        )
      end

      context "when enabled" do
        before do
          SiteSetting.pm_tags_allowed_for_groups = "1|2|3"
          SiteSetting.display_personal_messages_tag_counts = true
          sign_in(admin)
        end

        it "shows topic tags and pm tags" do
          get "/tags.json"
          tags = response.parsed_body["tags"]

          serialized_tag = tags.find { |t| t["name"] == topic_tag.name }
          expect(serialized_tag["count"]).to eq(2)
          expect(serialized_tag["pm_count"]).to eq(5)

          serialized_tag = tags.find { |t| t["name"] == test_tag.name }
          expect(serialized_tag["count"]).to eq(0)
          expect(serialized_tag["pm_count"]).to eq(1)
        end

        it "doesn't allow users to see other users' PMs" do
          non_admin_user = Fabricate(:trust_level_1)

          SiteSetting.pm_tags_allowed_for_groups = "1|2|3|11"
          sign_in(non_admin_user)

          get "/u/#{admin.username}/messages/tags/#{test_tag.name}.json"
          expect(response.status).to eq(404)
        end
      end

      context "when disabled" do
        before do
          SiteSetting.pm_tags_allowed_for_groups = ""
          sign_in(admin)
        end

        it "hides pm tags" do
          get "/tags.json"

          expect(response.parsed_body["tags"]).to match(
            [include(name: topic_tag.name, id: topic_tag.id)],
          )
        end
      end
    end

    context "with tags_listed_by_group enabled" do
      before { SiteSetting.tags_listed_by_group = true }
      include_examples "retrieves the right tags"

      it "works for tags in groups" do
        _tag_group = Fabricate(:tag_group, tags: [test_tag, topic_tag, synonym])

        get "/tags.json"

        expect(response.status).to eq(200)
        tags = response.parsed_body["tags"]
        expect(tags.length).to eq(0)
        group = response.parsed_body.dig("extras", "tag_groups")&.first
        expect(group).to be_present
        expect(group["tags"]).to match(
          [
            include(name: test_tag.name, id: test_tag.id),
            include(name: topic_tag.name, id: topic_tag.id),
          ],
        )
      end

      it "does not result in N+1 queries with multiple tag_groups" do
        tag_group1 = Fabricate(:tag_group, tags: [test_tag, topic_tag, synonym])

        # warm up
        get "/tags.json"
        expect(response.status).to eq(200)

        initial_sql_queries_count =
          track_sql_queries do
            get "/tags.json"

            expect(response.status).to eq(200)

            tag_groups = response.parsed_body.dig("extras", "tag_groups")

            expect(tag_groups.length).to eq(1)
            expect(tag_groups.map { |tag_group| tag_group["name"] }).to contain_exactly(
              tag_group1.name,
            )
          end.length

        tag_group2 = Fabricate(:tag_group, tags: [topic_tag])

        new_sql_queries_count =
          track_sql_queries do
            get "/tags.json"

            expect(response.status).to eq(200)

            tag_groups = response.parsed_body.dig("extras", "tag_groups")

            expect(tag_groups.length).to eq(2)
            expect(tag_groups.map { |tag_group| tag_group["name"] }).to contain_exactly(
              tag_group1.name,
              tag_group2.name,
            )
          end.length

        expect(new_sql_queries_count).to be <= initial_sql_queries_count
      end
    end

    context "with tags_listed_by_group disabled" do
      before { SiteSetting.tags_listed_by_group = false }
      include_examples "retrieves the right tags"

      it "returns the right tags and categories tags for admin user" do
        category.update!(tags: [test_tag])

        sign_in(admin)

        get "/tags.json"

        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]

        expect(tags.length).to eq(2)

        expect(tags[0]["name"]).to eq(test_tag.name)
        expect(tags[0]["text"]).to eq(test_tag.name)
        expect(tags[0]["description"]).to eq(test_tag.description)
        expect(tags[0]["count"]).to eq(0)
        expect(tags[0]["pm_count"]).to eq(nil)
        expect(tags[0]["target_tag"]).to eq(nil)

        expect(tags[1]["name"]).to eq(topic_tag.name)

        categories = response.parsed_body["extras"]["categories"]

        expect(categories[0]["id"]).to eq(category.id)
        expect(categories[0]["tags"].length).to eq(1)
        expect(categories[0]["tags"][0]["name"]).to eq(test_tag.name)
        expect(categories[0]["tags"][0]["text"]).to eq(test_tag.name)
        expect(categories[0]["tags"][0]["description"]).to eq(test_tag.description)
        expect(categories[0]["tags"][0]["count"]).to eq(0)
        expect(categories[0]["tags"][0]["pm_count"]).to eq(nil)
        expect(categories[0]["tags"][0]["target_tag"]).to eq(nil)
      end

      it "does not result in N+1 queries when there are multiple categories configured with tags for an admin user" do
        category.update!(tags: [test_tag])

        sign_in(admin)

        # warm up
        get "/tags.json"
        expect(response.status).to eq(200)

        tags = response.parsed_body["tags"]

        expect(tags.length).to eq(2)
        expect(tags.map { |tag| tag["name"] }).to eq([test_tag.name, topic_tag.name])

        initial_sql_queries_count =
          track_sql_queries do
            get "/tags.json"

            expect(response.status).to eq(200)

            tags = response.parsed_body["tags"]

            expect(tags.length).to eq(2)
            expect(tags.map { |tag| tag["name"] }).to eq([test_tag.name, topic_tag.name])

            categories = response.parsed_body["extras"]["categories"]

            expect(categories.length).to eq(1)
            expect(categories[0]["id"]).to eq(category.id)
            expect(categories[0]["tags"].map { |tag| tag["name"] }).to eq([test_tag.name])
          end.length

        category2 = Fabricate(:category, tags: [topic_tag])

        new_sql_queries_count =
          track_sql_queries do
            get "/tags.json"

            expect(response.status).to eq(200)

            tags = response.parsed_body["tags"]

            expect(tags.length).to eq(2)
            expect(tags.map { |tag| tag["name"] }).to eq([test_tag.name, topic_tag.name])

            categories = response.parsed_body["extras"]["categories"]

            expect(categories.length).to eq(2)
            expect(categories[0]["id"]).to eq(category.id)
            expect(categories[0]["tags"].map { |tag| tag["name"] }).to eq([test_tag.name])
            expect(categories[1]["id"]).to eq(category2.id)
            expect(categories[1]["tags"].map { |tag| tag["name"] }).to eq([topic_tag.name])
          end.length

        expect(new_sql_queries_count).to eq(initial_sql_queries_count)
      end
    end

    context "with hidden tags" do
      before { create_hidden_tags(["staff1"]) }

      it "is returned to admins" do
        sign_in(admin)
        get "/tags.json"
        expect(response.parsed_body["tags"].map { |t| t["text"] }).to include("staff1")
        expect(response.parsed_body["extras"]["categories"]).to be_empty
      end

      it "is not returned to anon" do
        get "/tags.json"
        expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
        expect(response.parsed_body["extras"]["categories"]).to be_empty
      end

      it "is not returned to regular user" do
        sign_in(user)
        get "/tags.json"
        expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
        expect(response.parsed_body["extras"]["categories"]).to be_empty
      end

      context "when restricted to a category" do
        before { category.tags = [Tag.find_by_name("staff1")] }

        it "is returned to admins" do
          sign_in(admin)
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to include("staff1")
          categories = response.parsed_body["extras"]["categories"]
          expect(categories.length).to eq(1)
          expect(categories.first["tags"].map { |t| t["text"] }).to include("staff1")
        end

        it "is not returned to anon" do
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
          expect(response.parsed_body["extras"]["categories"]).to be_empty
        end

        it "is not returned to regular user" do
          sign_in(user)
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
          expect(response.parsed_body["extras"]["categories"]).to be_empty
        end
      end

      context "when listed by group" do
        before { SiteSetting.tags_listed_by_group = true }

        it "is returned to admins" do
          sign_in(admin)
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
          tag_groups = response.parsed_body["extras"]["tag_groups"]
          expect(tag_groups.length).to eq(1)
          expect(tag_groups.first["tags"].map { |t| t["text"] }).to include("staff1")
        end

        it "is not returned to anon" do
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
          expect(response.parsed_body["extras"]["tag_groups"]).to be_empty
        end

        it "is not returned to regular user" do
          sign_in(user)
          get "/tags.json"
          expect(response.parsed_body["tags"].map { |t| t["text"] }).to_not include("staff1")
          expect(response.parsed_body["extras"]["tag_groups"]).to be_empty
        end
      end
    end
  end

  describe "#show" do
    fab!(:tag) { Fabricate(:tag, name: "test") }
    fab!(:topic_without_tags, :topic)
    fab!(:topic_with_tags) { Fabricate(:topic, tags: [tag]) }

    it "should return the right response" do
      get "/tag/test.json"

      expect(response.status).to eq(200)

      json = response.parsed_body
      topic_list = json["topic_list"]

      expect(topic_list["tags"].map { |t| t["id"] }).to contain_exactly(tag.id)
    end

    it "should handle invalid tags" do
      get "/tag/%2ftest%2f"
      expect(response.status).to eq(404)
    end

    it "should handle synonyms" do
      synonym = Fabricate(:tag, target_tag: tag)
      get "/tag/#{synonym.name}/l/top.json?period=daily"
      expect(response.status).to eq(301)
      expect(response.redirect_url).to match(%r{/tag/test/#{tag.id}/l/top\.json\?period=daily})
    end

    it "is not creating infinite redirect loop when tag is a synonym of itself" do
      tag.update!(target_tag_id: tag.id)

      get "/tag/#{tag.name}/l/top.json?period=daily"
      expect(response.status).to eq(200)
    end

    it "does not show staff-only tags" do
      _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

      get "/tag/test"
      expect(response.status).to eq(404)

      get "/tag/#{tag.id}.json"
      expect(response.status).to eq(404)

      sign_in(admin)

      get "/tag/#{tag.slug}/#{tag.id}"
      expect(response.status).to eq(200)
    end

    it "can handle additional tags in query params" do
      tag2 = Fabricate(:tag)
      topic_with_two_tags = Fabricate(:topic, tags: [tag, tag2])

      get "/tag/test.json?match_all_tags=true&tags[]=#{tag2.name}"
      expect(response.status).to eq(200)
      expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
        topic_with_two_tags.id,
      )
    end

    it "can handle duplicate tags in query params" do
      tag2 = Fabricate(:tag)
      topic_with_two_tags = Fabricate(:topic, tags: [tag, tag2])

      get "/tag/test.json?match_all_tags=true&tags[]=test&tags[]=#{tag2.name}"
      expect(response.status).to eq(200)
      expect(response.parsed_body["topic_list"]["topics"].map { |t| t["id"] }).to contain_exactly(
        topic_with_two_tags.id,
      )
    end

    it "puts the tag description in the meta description" do
      described_tag = Fabricate(:tag, name: "test2", description: "This is a description")
      get "/tag/#{described_tag.slug}/#{described_tag.id}"

      expect(response.status).to eq(200)

      expect(response.body).to include(
        "<meta name=\"description\" content=\"#{described_tag.description}\"",
      )
    end

    it "has a default description for tags without a description" do
      get "/tag/#{tag.slug}/#{tag.id}"

      expect(response.status).to eq(200)

      expect(response.body).to include(
        "<meta name=\"description\" content=\"Topics tagged #{tag.name}\"",
      )
    end

    it "handles special tag 'none'" do
      SiteSetting.pm_tags_allowed_for_groups = "1|2|3"

      sign_in(admin)

      get "/tag/none.json"
      expect(response.parsed_body["topic_list"]["topics"].length).to eq(1)
    end

    it "handles numeric tag names via legacy /tag/:tag_name route" do
      numeric_tag = Fabricate(:tag, name: "6309")
      Fabricate(:topic, tags: [numeric_tag])

      get "/tag/6309"
      expect(response.status).to eq(200)
      expect(response.body).not_to include("ActionView::Template::Error")
    end

    it "handles numeric tag names via legacy /tag/:tag_name route with filters" do
      numeric_tag = Fabricate(:tag, name: "6309")
      Fabricate(:topic, tags: [numeric_tag])

      get "/tag/6309/l/latest"
      expect(response.status).to eq(200)
      expect(response.body).not_to include("ActionView::Template::Error")
    end

    context "with a category in the path" do
      fab!(:topic_in_category) { Fabricate(:topic, tags: [tag], category: category) }

      fab!(:topic_in_category_without_tag) { Fabricate(:topic, category: category) }

      fab!(:topic_out_of_category) { Fabricate(:topic, tags: [tag]) }

      it "should produce the topic inside the category and not the topic outside of it" do
        get "/tags/c/#{category.slug}/#{tag.name}.json"

        topic_ids = response.parsed_body["topic_list"]["topics"].map { |x| x["id"] }
        expect(topic_ids).to include(topic_in_category.id)
        expect(topic_ids).to_not include(topic_out_of_category.id)
        expect(topic_ids).to_not include(topic_in_category_without_tag.id)
      end

      it "should produce the right next topic URL" do
        get "/tags/c/#{category.slug_path.join("/")}/#{category.id}/#{tag.name}.json?per_page=1"

        expect(response.parsed_body["topic_list"]["more_topics_url"]).to start_with(
          "/tags/c/#{category.slug_path.join("/")}/#{category.id}/#{tag.name}",
        )
      end

      it "should 404 for invalid category path" do
        get "/tags/c/#{category.slug_path.join("/")}/#{category.id}/somerandomstring/#{tag.name}.json?per_page=1"

        expect(response.status).to eq(404)
      end

      it "should 404 for secure categories" do
        c = Fabricate(:private_category, group: Fabricate(:group))
        get "/tags/c/#{c.slug_path.join("/")}/#{c.id}/#{tag.name}.json"

        expect(response.status).to eq(404)
      end
    end

    context "with a subcategory in the path" do
      fab!(:topic_in_subcategory) { Fabricate(:topic, tags: [tag], category: subcategory) }

      fab!(:topic_in_subcategory_without_tag) { Fabricate(:topic, category: subcategory) }

      fab!(:topic_out_of_subcategory) { Fabricate(:topic, tags: [tag]) }

      it "should produce the topic inside the subcategory and not the topic outside of it" do
        get "/tags/c/#{category.slug}/#{subcategory.slug}/#{tag.name}.json"

        topic_ids = response.parsed_body["topic_list"]["topics"].map { |x| x["id"] }
        expect(topic_ids).to include(topic_in_subcategory.id)
        expect(topic_ids).to_not include(topic_out_of_subcategory.id)
        expect(topic_ids).to_not include(topic_in_subcategory_without_tag.id)
      end
    end

    it "should ignore invalid tag parameter" do
      get "/tag/test.json?tags[0]=nada"
      expect(response.status).to eq(200)
    end
  end

  describe "#info" do
    fab!(:tag) { Fabricate(:tag, name: "test") }
    let(:synonym) { Fabricate(:tag, name: "synonym", target_tag: tag) }

    it "returns 404 if tag not found" do
      get "/tag/nope/info.json"
      expect(response.status).to eq(404)
    end

    it "can handle tag with no synonyms" do
      get "/tag/#{tag.name}/info.json"
      expect(response.status).to eq(200)
      expect(response.parsed_body.dig("tag_info", "name")).to eq(tag.name)
      expect(response.parsed_body.dig("tag_info", "synonyms")).to be_empty
      expect(response.parsed_body.dig("tag_info", "category_ids")).to be_empty
      expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(false)
    end

    it "can handle a synonym" do
      get "/tag/#{synonym.name}/info.json"
      expect(response.status).to eq(200)
      expect(response.parsed_body.dig("tag_info", "name")).to eq(synonym.name)
      expect(response.parsed_body.dig("tag_info", "synonyms")).to be_empty
      expect(response.parsed_body.dig("tag_info", "category_ids")).to be_empty
      expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(false)
    end

    it "can return a tag's synonyms" do
      synonym
      get "/tag/#{tag.name}/info.json"
      expect(response.status).to eq(200)
      expect(response.parsed_body.dig("tag_info", "synonyms").map { |t| t["text"] }).to eq(
        [synonym.name],
      )
    end

    it "returns 404 if tag is staff-only" do
      _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])
      get "/tag/test/info.json"
      expect(response.status).to eq(404)

      get "/tag/#{tag.id}/info.json"
      expect(response.status).to eq(404)
    end

    it "staff-only tags can be retrieved for staff user" do
      sign_in(admin)
      _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])
      get "/tag/test/info.json"
      expect(response.status).to eq(200)
    end

    it "can return category restrictions" do
      category.update!(tags: [tag])
      category2 = Fabricate(:category)
      tag_group = Fabricate(:tag_group, tags: [tag])
      category2.update!(tag_groups: [tag_group])
      _staff_category = Fabricate(:private_category, group: Fabricate(:group), tags: [tag])
      get "/tag/#{tag.name}/info.json"
      expect(response.parsed_body.dig("tag_info", "category_ids")).to contain_exactly(
        category.id,
        category2.id,
      )
      expect(response.parsed_body["categories"]).to be_present
      expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(true)
    end

    context "when tag belongs to a tag group" do
      fab!(:tag_group) { Fabricate(:tag_group, tags: [tag]) }

      it "returns tag groups if tag groups are visible" do
        SiteSetting.tags_listed_by_group = true
        get "/tag/#{tag.name}/info.json"
        expect(response.parsed_body.dig("tag_info", "tag_group_names")).to eq([tag_group.name])
      end

      it "doesn't return tag groups if tag groups aren't visible" do
        SiteSetting.tags_listed_by_group = false
        get "/tag/#{tag.name}/info.json"
        expect(response.parsed_body["tag_info"].has_key?("tag_group_names")).to eq(false)
      end

      context "when restricted to a private category" do
        let!(:private_category) do
          Fabricate(
            :private_category,
            group: Fabricate(:group),
            tag_groups: [tag_group],
            allow_global_tags: true,
          )
        end

        it "can return categories to users who can access them" do
          sign_in(admin)
          get "/tag/#{tag.name}/info.json"
          expect(response.parsed_body.dig("tag_info", "category_ids")).to contain_exactly(
            private_category.id,
          )
          expect(response.parsed_body["categories"]).to be_present
          expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(true)
        end

        it "can indicate category restriction to users who can't access them" do
          sign_in(user)
          get "/tag/#{tag.name}/info.json"
          expect(response.parsed_body.dig("tag_info", "category_ids")).to be_empty
          expect(response.parsed_body["categories"]).to be_blank
          expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(true)
        end

        it "can indicate category restriction to anon" do
          get "/tag/#{tag.name}/info.json"
          expect(response.parsed_body.dig("tag_info", "category_ids")).to be_empty
          expect(response.parsed_body["categories"]).to be_blank
          expect(response.parsed_body.dig("tag_info", "category_restricted")).to eq(true)
        end
      end
    end
  end

  describe "#tag_feed" do
    fab!(:tag) { Fabricate(:tag, name: "test") }

    it "returns 404 if tag is staff-only" do
      _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

      get "/tag/test.rss"
      expect(response.status).to eq(404)

      get "/tag/#{tag.slug}/#{tag.id}.rss"
      expect(response.status).to eq(404)
    end
  end

  describe "#settings" do
    fab!(:tag) { Fabricate(:tag, name: "test-tag", description: "test description") }
    fab!(:synonym) { Fabricate(:tag, name: "test-synonym", target_tag: tag) }

    it "returns 404 if tag not found" do
      sign_in(admin)
      get "/tag/9999999/settings.json"
      expect(response.status).to eq(404)
    end

    it "returns 404 if user cannot edit tags" do
      sign_in(user)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(404)
    end

    it "returns tag settings for admin" do
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["id"]).to eq(tag.id)
      expect(settings["name"]).to eq(tag.name)
      expect(settings["slug"]).to eq(tag.slug)
      expect(settings["description"]).to eq(tag.description)
      expect(settings["can_edit"]).to eq(true)
      expect(settings["can_admin"]).to eq(true)
    end

    it "includes synonyms in the response" do
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["synonyms"].map { |s| s["name"] }).to contain_exactly(synonym.name)
    end

    it "includes category restrictions when present" do
      category.update!(tags: [tag])
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["category_restricted"]).to eq(true)
      # categories are at top level, not nested inside tag_settings
      expect(response.parsed_body["categories"].map { |c| c["id"] }).to contain_exactly(category.id)
    end

    it "includes tag group names for admin" do
      SiteSetting.tags_listed_by_group = true
      tag_group = Fabricate(:tag_group, tags: [tag])
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["tag_group_names"]).to contain_exactly(tag_group.name)
    end

    it "includes tag groups with id and name for admin" do
      SiteSetting.tags_listed_by_group = true
      tag_group = Fabricate(:tag_group, tags: [tag])
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["tag_groups"]).to be_present
      expect(settings["tag_groups"].length).to eq(1)
      expect(settings["tag_groups"][0]["id"]).to eq(tag_group.id)
      expect(settings["tag_groups"][0]["name"]).to eq(tag_group.name)
    end

    it "returns slug_for_url when tag has an empty slug" do
      tag.update_column(:slug, "")
      sign_in(admin)
      get "/tag/#{tag.id}/settings.json"
      expect(response.status).to eq(200)

      settings = response.parsed_body["tag_settings"]
      expect(settings["slug"]).to eq("#{tag.id}-tag")
    end

    context "with content localization enabled" do
      before { SiteSetting.content_localization_enabled = true }

      it "includes localizations in the response" do
        Fabricate(:tag_localization, tag: tag, locale: "zh_CN", name: "测试标签")
        sign_in(admin)
        get "/tag/#{tag.id}/settings.json"
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["localizations"]).to be_present
        expect(settings["localizations"].first["locale"]).to eq("zh_CN")
        expect(settings["localizations"].first["name"]).to eq("测试标签")
      end
    end

    context "when user in edit_tags_allowed_groups" do
      before { SiteSetting.edit_tags_allowed_groups = "1|2|13" }

      it "allows access for users in allowed groups" do
        sign_in(regular_user)
        get "/tag/#{tag.id}/settings.json"
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["can_edit"]).to eq(true)
        expect(settings["can_admin"]).to eq(false)
      end
    end
  end

  describe "#update_settings" do
    fab!(:tag) { Fabricate(:tag, name: "original-name", description: "original description") }

    it "returns 404 if tag not found" do
      sign_in(admin)
      put "/tag/9999999/settings.json", params: { tag_settings: { name: "new-name" } }
      expect(response.status).to eq(404)
    end

    it "returns 404 if user cannot edit tags" do
      sign_in(user)
      put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "new-name" } }
      expect(response.status).to eq(404)
    end

    context "when signed in as admin" do
      before { sign_in(admin) }

      it "updates the tag name" do
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "updated-name" } }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["name"]).to eq("updated-name")
        expect(tag.reload.name).to eq("updated-name")
      end

      it "updates the tag slug" do
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { slug: "custom-slug" } }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["slug"]).to eq("custom-slug")
        expect(tag.reload.slug).to eq("custom-slug")
      end

      it "updates the tag description" do
        put "/tag/#{tag.id}/settings.json",
            params: {
              tag_settings: {
                description: "new description",
              },
            }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["description"]).to eq("new description")
        expect(tag.reload.description).to eq("new description")
      end

      it "can clear the description" do
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { description: "" } }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["description"]).to be_blank
        expect(tag.reload.description).to be_blank
      end

      it "logs staff action when renaming a tag" do
        expect {
          put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "renamed-tag" } }
        }.to change { UserHistory.where(action: UserHistory.actions[:custom_staff]).count }.by(1)

        log = UserHistory.last
        expect(log.custom_type).to eq("renamed_tag")
        expect(log.previous_value).to eq("original-name")
        expect(log.new_value).to eq("renamed-tag")
      end

      it "does not log staff action when not changing name" do
        expect {
          put "/tag/#{tag.id}/settings.json",
              params: {
                tag_settings: {
                  description: "just updating description",
                },
              }
        }.not_to change { UserHistory.where(action: UserHistory.actions[:custom_staff]).count }
      end

      it "cleans the tag name" do
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "New Tag Name" } }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["name"]).to eq("new-tag-name")
      end

      it "ignores empty name param" do
        original_name = tag.name
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "" } }
        expect(response.status).to eq(200)
        expect(tag.reload.name).to eq(original_name)
      end

      it "returns errors for duplicate tag name" do
        other_tag = Fabricate(:tag, name: "existing-tag")
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: other_tag.name } }
        expect(response.status).to eq(422)
      end

      it "supports unicode tag names" do
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "日本語タグ" } }
        expect(response.status).to eq(200)

        settings = response.parsed_body["tag_settings"]
        expect(settings["name"]).to eq("日本語タグ")
      end
    end

    context "when user in edit_tags_allowed_groups" do
      before { SiteSetting.edit_tags_allowed_groups = "1|2|13" }

      it "allows updating for users in allowed groups" do
        sign_in(regular_user)
        put "/tag/#{tag.id}/settings.json", params: { tag_settings: { name: "user-updated" } }
        expect(response.status).to eq(200)
        expect(tag.reload.name).to eq("user-updated")
      end
    end
  end

  describe "#update" do
    fab!(:tag)

    before do
      tag
      sign_in(admin)
    end

    it "shows an unsupported error for tag id parameter" do
      put "/tag/#{tag.name}.json", params: { tag: { id: "new-id" } }

      expect(response.status).to eq(422)
      expect(response.parsed_body["errors"]).to include(
        "Updating a tag name by `id` attribute is unsupported. Use the `name` attribute instead.",
      )
    end

    it "triggers a extensibility event" do
      event =
        DiscourseEvent
          .track_events { put "/tag/#{tag.name}.json", params: { tag: { name: "hello" } } }
          .last

      expect(event[:event_name]).to eq(:tag_updated)
      expect(event[:params].first).to eq(tag)
    end

    it "updates the tag" do
      put "/tag/#{tag.name}.json", params: { tag: { description: "New description" } }

      expect(response.status).to eq(200)
      expect(tag.reload.description).to eq("New description")
    end

    it "can clear the tag description" do
      tag.update!(description: "existing description")

      put "/tag/#{tag.name}.json", params: { tag: { description: "" } }

      expect(response.status).to eq(200)
      expect(tag.reload.description).to be_blank
    end

    it "returns 403 for non-admins" do
      sign_in(regular_user)
      put "/tag/#{tag.name}.json", params: { tag: { description: "New description" } }

      expect(response.status).to eq(403)
    end

    it "returns 404 when tag is hidden and user is not in permitted group" do
      SiteSetting.edit_tags_allowed_groups = "1|2|3|13"
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name])
      sign_in(regular_user)
      put "/tag/#{tag.name}.json", params: { tag: { description: "New description" } }

      expect(response.status).to eq(404)
    end

    it "returns 404 for non-existing tags" do
      put "/tag/nonexistenttag.json", params: { tag: { description: "New description" } }

      expect(response.status).to eq(404)
    end

    it "logs the update into a UserHistory" do
      put "/tag/#{tag.name}.json", params: { tag: { name: "new tag" } }

      expect(response.status).to eq(200)

      history = UserHistory.where(action: UserHistory.actions[:custom_staff]).last
      expect(history).to have_attributes(
        custom_type: "renamed_tag",
        acting_user_id: admin.id,
        previous_value: tag.name,
        new_value: "new-tag",
      )
    end

    it "does not log a UserHistory if the tag name is not changed" do
      expect {
        put "/tag/#{tag.name}.json", params: { tag: { description: "Updated description" } }
      }.to_not change { UserHistory.count }
    end
  end

  describe "#personal_messages" do
    fab!(:personal_message) do
      Fabricate(
        :private_message_topic,
        user: regular_user,
        topic_allowed_users: [
          Fabricate.build(:topic_allowed_user, user: regular_user),
          Fabricate.build(:topic_allowed_user, user: moderator),
          Fabricate.build(:topic_allowed_user, user: admin),
        ],
      )
    end

    fab!(:tag) { Fabricate(:tag, topics: [personal_message], name: "test") }

    before { SiteSetting.pm_tags_allowed_for_groups = "1|2|3" }

    context "as a regular user" do
      it "can't see pm tags" do
        get "/tags/personal_messages/#{regular_user.username}.json"

        expect(response.status).to eq(403)
      end
    end

    context "as an moderator" do
      before { sign_in(moderator) }

      it "can't see pm tags for regular user" do
        get "/tags/personal_messages/#{regular_user.username}.json"

        expect(response.status).to eq(404)
      end

      it "can see their own pm tags" do
        get "/tags/personal_messages/#{moderator.username}.json"

        expect(response.status).to eq(200)

        tag = response.parsed_body["tags"]
        expect(tag[0]["name"]).to eq("test")
      end
    end

    context "as an admin" do
      before { sign_in(admin) }

      it "can see pm tags for regular user" do
        get "/tags/personal_messages/#{regular_user.username}.json"

        expect(response.status).to eq(200)

        tag = response.parsed_body["tags"]
        expect(tag[0]["name"]).to eq("test")
      end

      it "can see their own pm tags" do
        get "/tags/personal_messages/#{admin.username}.json"

        expect(response.status).to eq(200)

        tag = response.parsed_body["tags"]
        expect(tag[0]["name"]).to eq("test")
      end

      it "works with usernames with a period" do
        admin.update!(username: "test.test")

        get "/tags/personal_messages/#{admin.username}.json"

        expect(response.status).to eq(200)
      end
    end
  end

  describe "#show_latest" do
    fab!(:tag)
    fab!(:other_tag, :tag)
    fab!(:third_tag, :tag)

    fab!(:single_tag_topic) { Fabricate(:topic, tags: [tag]) }
    fab!(:multi_tag_topic) { Fabricate(:topic, tags: [tag, other_tag]) }
    fab!(:all_tag_topic) { Fabricate(:topic, tags: [tag, other_tag, third_tag]) }

    context "with tagging disabled" do
      it "returns 404" do
        SiteSetting.tagging_enabled = false
        get "/tag/#{tag.name}/l/latest.json"
        expect(response.status).to eq(404)
      end
    end

    context "with tagging enabled" do
      def parse_topic_ids
        response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }
      end

      it "can filter by tag" do
        get "/tag/#{tag.name}/l/latest.json"
        expect(response.status).to eq(200)
      end

      it "can render a topic list from the latest endpoint" do
        get "/tag/#{tag.slug}/#{tag.id}/l/latest"
        expect(response.status).to eq(200)
        expect(response.body).to include("topic-list")
      end

      it "can filter by two tags" do
        single_tag_topic
        multi_tag_topic
        all_tag_topic

        get "/tag/#{tag.name}/l/latest.json", params: { additional_tag_names: other_tag.name }

        expect(response.status).to eq(200)

        topic_ids = parse_topic_ids
        expect(topic_ids).to include(all_tag_topic.id)
        expect(topic_ids).to include(multi_tag_topic.id)
        expect(topic_ids).to_not include(single_tag_topic.id)
      end

      it "can filter by multiple tags" do
        single_tag_topic
        multi_tag_topic
        all_tag_topic

        get "/tag/#{tag.name}/l/latest.json",
            params: {
              additional_tag_names: "#{other_tag.name}/#{third_tag.name}",
            }

        expect(response.status).to eq(200)

        topic_ids = parse_topic_ids
        expect(topic_ids).to include(all_tag_topic.id)
        expect(topic_ids).to_not include(multi_tag_topic.id)
        expect(topic_ids).to_not include(single_tag_topic.id)
      end

      it "does not find any tags when a tag which doesn't exist is passed" do
        single_tag_topic

        get "/tag/#{tag.name}/l/latest.json", params: { additional_tag_names: "notatag" }

        expect(response.status).to eq(200)

        topic_ids = parse_topic_ids
        expect(topic_ids).to_not include(single_tag_topic.id)
      end

      it "can filter by category and tag" do
        get "/tags/c/#{category.slug}/#{tag.name}/l/latest.json"
        expect(response.status).to eq(200)
      end

      it "can filter by category, sub-category, and tag" do
        get "/tags/c/#{category.slug}/#{subcategory.slug}/#{tag.name}/l/latest.json"
        expect(response.status).to eq(200)
      end

      it "can filter by category, no sub-category, and tag" do
        get "/tags/c/#{category.slug}/none/#{tag.name}/l/latest.json"
        expect(response.status).to eq(200)
      end

      it "can handle subcategories with the same name" do
        category2 = Fabricate(:category)
        subcategory2 =
          Fabricate(
            :category,
            parent_category_id: category2.id,
            name: subcategory.name,
            slug: subcategory.slug,
          )
        t = Fabricate(:topic, category_id: subcategory2.id, tags: [other_tag])
        get "/tags/c/#{category2.slug}/#{subcategory2.slug}/#{other_tag.name}/l/latest.json"

        expect(response.status).to eq(200)

        topic_ids = parse_topic_ids
        expect(topic_ids).to include(t.id)
      end

      context "when logged in" do
        before { sign_in(user) }

        it "can filter by bookmarked" do
          get "/tag/#{tag.name}/l/bookmarks.json"

          expect(response.status).to eq(200)
        end

        it "returns a 404 when tag is restricted" do
          _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

          get "/tag/test/l/latest.json"
          expect(response.status).to eq(404)

          sign_in(admin)

          get "/tag/test/l/latest.json"
          expect(response.status).to eq(200)
        end

        context "with muted tags" do
          before do
            TagUser.create!(
              user_id: user.id,
              tag_id: tag.id,
              notification_level: CategoryUser.notification_levels[:muted],
            )
          end

          it "includes topics when filtered by muted tag" do
            single_tag_topic

            get "/tag/#{tag.name}/l/latest.json"
            expect(response.status).to eq(200)

            topic_ids = parse_topic_ids
            expect(topic_ids).to include(single_tag_topic.id)
          end

          it "includes topics when filtered by category and muted tag" do
            category = Fabricate(:category)
            single_tag_topic.update!(category: category)

            get "/tags/c/#{category.slug}/#{tag.name}/l/latest.json"
            expect(response.status).to eq(200)

            topic_ids = parse_topic_ids
            expect(topic_ids).to include(single_tag_topic.id)
          end
        end
      end
    end
  end

  describe "#show_top" do
    fab!(:tag)

    fab!(:category)
    fab!(:topic) { Fabricate(:topic, category: category) }
    fab!(:tag_topic) { Fabricate(:topic, category: category, tags: [tag]) }
    fab!(:tag_topic2) { Fabricate(:topic, category: category, tags: [tag]) }

    before do
      SiteSetting.top_page_default_timeframe = "all"
      TopTopic.create!(topic: topic, all_score: 1)
      TopTopic.create!(topic: tag_topic, all_score: 1)
      TopTopic.create!(topic: tag_topic2, daily_score: 1)
    end

    it "can filter by tag" do
      get "/tag/#{tag.name}/l/top.json"
      expect(response.status).to eq(200)

      topic_ids = response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }
      expect(topic_ids).to eq([tag_topic.id])
    end

    it "can filter by tag and period" do
      get "/tag/#{tag.name}/l/top.json?period=daily"
      expect(response.status).to eq(200)

      list = response.parsed_body["topic_list"]
      topic_ids = list["topics"].map { |topic| topic["id"] }
      expect(topic_ids).to eq([tag_topic2.id])
      expect(list["for_period"]).to eq("daily")
    end

    it "can filter by both category and tag" do
      get "/tags/c/#{category.slug}/#{category.id}/#{tag.name}/l/top.json"
      expect(response.status).to eq(200)

      topic_ids = response.parsed_body["topic_list"]["topics"].map { |topic| topic["id"] }
      expect(topic_ids).to eq([tag_topic.id])
    end

    it "raises an error if the period is not valid" do
      get "/tag/#{tag.name}/l/top.json?period=decadely"
      expect(response.status).to eq(400)
    end

    it "returns a 404 if tag is restricted" do
      _tag_group = Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: ["test"])

      get "/tag/test/l/top.json"
      expect(response.status).to eq(404)

      sign_in(admin)

      get "/tag/test/l/top.json"
      expect(response.status).to eq(200)
    end
  end

  describe "#search" do
    context "with tagging disabled" do
      it "returns 404" do
        SiteSetting.tagging_enabled = false
        get "/tags/filter/search.json", params: { q: "stuff" }
        expect(response.status).to eq(404)
      end
    end

    context "with tagging enabled" do
      it "can return some tags" do
        stuff = Fabricate(:tag, name: "stuff")
        stumped = Fabricate(:tag, name: "stumped")
        Fabricate(:tag, name: "stinky")

        get "/tags/filter/search.json", params: { q: "stu" }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match(
          [include(name: "stuff", id: stuff.id), include(name: "stumped", id: stumped.id)],
        )
      end

      it "returns tags ordered by public_topic_count, and prioritises exact matches" do
        tag = Fabricate(:tag, name: "tag", public_topic_count: 1, staff_topic_count: 1)
        tag1 = Fabricate(:tag, name: "tag1", public_topic_count: 10, staff_topic_count: 10)
        tag2 = Fabricate(:tag, name: "tag2", public_topic_count: 100, staff_topic_count: 100)

        get "/tags/filter/search.json", params: { q: "tag", limit: 2 }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match(
          [include(name: tag.name, id: tag.id), include(name: tag2.name, id: tag2.id)],
        )
      end

      context "with category restriction" do
        fab!(:yup) { Fabricate(:tag, name: "yup") }
        fab!(:category) { Fabricate(:category, tags: [yup]) }

        it "can say if given tag is not allowed" do
          nope = Fabricate(:tag, name: "nope")
          get "/tags/filter/search.json", params: { q: nope.name, categoryId: category.id }
          expect(response.status).to eq(200)
          expect(response.parsed_body["results"].map { |j| j["id"] }.sort).to eq([])
          expect(response.parsed_body["forbidden"]).to be_present
          expect(response.parsed_body["forbidden_message"]).to eq(
            I18n.t("tags.forbidden.in_this_category", tag_name: nope.name),
          )
        end

        it "can say if given tag is restricted to different category" do
          category
          get "/tags/filter/search.json",
              params: {
                q: yup.name,
                categoryId: Fabricate(:category).id,
              }
          expect(response.parsed_body["results"].map { |j| j["id"] }.sort).to eq([])
          expect(response.parsed_body["forbidden"]).to be_present
          expect(response.parsed_body["forbidden_message"]).to eq(
            I18n.t(
              "tags.forbidden.restricted_to",
              count: 1,
              tag_name: yup.name,
              category_names: category.name,
            ),
          )
        end

        it "returns category-restricted tag as disabled for input" do
          category
          get "/tags/filter/search.json",
              params: {
                q: yup.name,
                filterForInput: true,
                categoryId: Fabricate(:category).id,
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == yup.name }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
          expect(result["title"]).to eq(
            I18n.t(
              "tags.forbidden.restricted_to",
              count: 1,
              tag_name: yup.name,
              category_names: category.name,
            ),
          )
        end

        it "returns category-restricted tag as disabled for partial match" do
          category
          get "/tags/filter/search.json",
              params: {
                q: "yu",
                filterForInput: true,
                categoryId: Fabricate(:category).id,
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == yup.name }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
        end

        it "truncates category names when tag is restricted to many categories" do
          categories = [category] + (1..4).map { |i| Fabricate(:category, tags: [yup]) }

          get "/tags/filter/search.json",
              params: {
                q: yup.name,
                filterForInput: true,
                categoryId: Fabricate(:category).id,
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == yup.name }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)

          sorted_names = categories.map(&:name).sort
          expect(result["title"]).to eq(
            I18n.t(
              "tags.forbidden.restricted_to_truncated",
              tag_name: yup.name,
              category_names: sorted_names.first(3).join(", "),
              more_count: 2,
            ),
          )
        end

        it "truncates category names in forbidden_message when restricted to many categories" do
          categories = [category] + (1..4).map { |i| Fabricate(:category, tags: [yup]) }

          get "/tags/filter/search.json",
              params: {
                q: yup.name,
                categoryId: Fabricate(:category).id,
              }

          sorted_names = categories.map(&:name).sort
          expect(response.parsed_body["forbidden_message"]).to eq(
            I18n.t(
              "tags.forbidden.restricted_to_truncated",
              tag_name: yup.name,
              category_names: sorted_names.first(3).join(", "),
              more_count: 2,
            ),
          )
        end

        it "can filter on category without q param" do
          Fabricate(:tag, name: "nope")
          get "/tags/filter/search.json", params: { categoryId: category.id }
          expect(response.status).to eq(200)

          expect(response.parsed_body["results"]).to match([include(name: yup.name, id: yup.id)])
        end
      end

      context "with synonyms" do
        fab!(:tag) { Fabricate(:tag, name: "plant") }
        fab!(:synonym) { Fabricate(:tag, name: "plants", target_tag: tag) }

        it "can return synonyms" do
          get "/tags/filter/search.json", params: { q: "plant" }

          expect(response.status).to eq(200)
          expect(response.parsed_body["results"]).to match(
            [include(name: tag.name, id: tag.id), include(name: synonym.name, id: synonym.id)],
          )
        end

        it "can omit synonyms" do
          get "/tags/filter/search.json", params: { q: "plant", excludeSynonyms: "true" }
          expect(response.status).to eq(200)

          expect(response.parsed_body["results"]).to match([include(name: tag.name, id: tag.id)])
        end

        it "returns synonym as disabled when excluded for input" do
          get "/tags/filter/search.json",
              params: {
                q: "plants",
                filterForInput: true,
                excludeSynonyms: "true",
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "plants" }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
          expect(result["title"]).to eq(I18n.t("tags.forbidden.synonym", tag_name: tag.name))
        end

        it "can return a message about synonyms not being allowed" do
          get "/tags/filter/search.json", params: { q: "plants", excludeSynonyms: "true" }
          expect(response.status).to eq(200)
          expect(response.parsed_body["results"].map { |j| j["id"] }.sort).to eq([])
          expect(response.parsed_body["forbidden"]).to be_present
          expect(response.parsed_body["forbidden_message"]).to eq(
            I18n.t("tags.forbidden.synonym", tag_name: tag.name),
          )
        end
      end

      context "with one_per_topic tag group" do
        fab!(:workflow_tag1) { Fabricate(:tag, name: "todo") }
        fab!(:workflow_tag2) { Fabricate(:tag, name: "ready-to-deploy") }
        fab!(:tag_group) do
          Fabricate(
            :tag_group,
            name: "Workflow",
            tags: [workflow_tag1, workflow_tag2],
            one_per_topic: true,
          )
        end

        it "returns sibling tag as disabled when already selected from group" do
          get "/tags/filter/search.json",
              params: {
                q: "ready-to-deploy",
                filterForInput: true,
                selected_tag_ids: [workflow_tag1.id],
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "ready-to-deploy" }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
          expect(result["title"]).to eq(
            I18n.t("tags.forbidden.one_tag_per_topic_group", tag_group_name: "Workflow"),
          )
        end

        it "returns sibling tag as disabled for partial match" do
          get "/tags/filter/search.json",
              params: {
                q: "ready",
                filterForInput: true,
                selected_tag_ids: [workflow_tag1.id],
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "ready-to-deploy" }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
        end

        it "returns tag as enabled when no sibling is selected" do
          get "/tags/filter/search.json", params: { q: "ready-to-deploy", filterForInput: true }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "ready-to-deploy" }
          expect(result).to be_present
          expect(result["disabled"]).to be_nil
        end
      end

      context "with parent tag group" do
        fab!(:parent_tag) { Fabricate(:tag, name: "vehicles") }
        fab!(:child_tag) { Fabricate(:tag, name: "sedan") }
        fab!(:tag_group) do
          Fabricate(:tag_group, name: "Vehicle Types", tags: [child_tag], parent_tag: parent_tag)
        end

        it "returns child tag as disabled when parent is not selected" do
          get "/tags/filter/search.json", params: { q: "sedan", filterForInput: true }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "sedan" }
          expect(result).to be_present
          expect(result["disabled"]).to eq(true)
          expect(result["title"]).to eq(
            I18n.t(
              "tags.forbidden.missing_parent_tag",
              parent_tag_name: "vehicles",
              tag_group_name: "Vehicle Types",
            ),
          )
        end

        it "returns child tag as enabled when parent is selected" do
          get "/tags/filter/search.json",
              params: {
                q: "sedan",
                filterForInput: true,
                selected_tag_ids: [parent_tag.id],
              }

          expect(response.status).to eq(200)
          result = response.parsed_body["results"].find { |t| t["name"] == "sedan" }
          expect(result).to be_present
          expect(result["disabled"]).to be_nil
        end

        it "does not leak hidden parent tag name" do
          staff_group = Fabricate(:group)
          parent_tag_group = Fabricate(:tag_group, tags: [parent_tag])
          parent_tag_group.permissions = [
            [staff_group.id, TagGroupPermission.permission_types[:full]],
          ]
          parent_tag_group.save!

          get "/tags/filter/search.json", params: { q: "sedan", filterForInput: true }

          expect(response.status).to eq(200)
          disabled_result = response.parsed_body["results"].find { |t| t["name"] == "sedan" }
          expect(disabled_result["title"]).not_to include("vehicles") if disabled_result
          expect(response.parsed_body["forbidden_message"]).to be_nil
        end
      end

      context "with staff-only tag group" do
        fab!(:hidden_tag) { Fabricate(:tag, name: "staff-only-tag") }
        fab!(:group)
        fab!(:tag_group) do
          Fabricate(:tag_group, tags: [hidden_tag]).tap do |tg|
            tg.permissions = [[group.id, TagGroupPermission.permission_types[:full]]]
            tg.save!
          end
        end

        it "does not leak hidden tag existence via forbidden message" do
          get "/tags/filter/search.json", params: { q: "staff-only-tag", filterForInput: true }

          expect(response.status).to eq(200)
          expect(response.parsed_body["results"]).to be_empty
          expect(response.parsed_body["forbidden"]).to be_nil
          expect(response.parsed_body["forbidden_message"]).to be_nil
        end
      end

      it "matches tags after sanitizing input" do
        Fabricate(:tag, name: "yup")
        nope = Fabricate(:tag, name: "nope")

        get "/tags/filter/search.json", params: { q: "N/ope" }

        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match([include(name: nope.name, id: nope.id)])
      end

      it "can return tags that are in secured categories but are allowed to be used" do
        c = Fabricate(:private_category, group: Fabricate(:group))
        tag = Fabricate(:tag, name: "cooltag")
        Fabricate(:topic, category: c, tags: [tag])

        get "/tags/filter/search.json", params: { q: "cool" }

        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match([include(name: tag.name, id: tag.id)])
      end

      it "supports Chinese and Russian" do
        chinese_tag = Fabricate(:tag, name: "房屋买卖")
        russian_tag = Fabricate(:tag, name: "тестовая-тема")

        get "/tags/filter/search.json", params: { q: "房" }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match(
          [include(name: chinese_tag.name, id: chinese_tag.id)],
        )

        get "/tags/filter/search.json", params: { q: "тема" }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match(
          [include(name: russian_tag.name, id: russian_tag.id)],
        )
      end

      it "can return all the results" do
        tag_group1 = Fabricate(:tag_group, tag_names: %w[common1 common2 group1tag group1tag2])
        _tag_group2 = Fabricate(:tag_group, tag_names: %w[common1 common2])
        category = Fabricate(:category, tag_groups: [tag_group1])
        get "/tags/filter/search.json",
            params: {
              q: "",
              limit: 5,
              categoryId: category.id,
              filterForInput: "true",
            }

        expect(response.status).to eq(200)
        expect(response.parsed_body["results"]).to match(
          [
            include(name: "common1"),
            include(name: "common2"),
            include(name: "group1tag"),
            include(name: "group1tag2"),
          ],
        )
      end

      describe "when limit params is invalid" do
        include_examples "invalid limit params",
                         "/tags/filter/search.json",
                         SiteSetting.max_tag_search_results
      end

      it "includes required tag group information" do
        tag1 = Fabricate(:tag)
        tag2 = Fabricate(:tag)

        tag_group = Fabricate(:tag_group, tags: [tag1, tag2])
        crtg = CategoryRequiredTagGroup.new(tag_group: tag_group, min_count: 1)
        category = Fabricate(:category, category_required_tag_groups: [crtg])

        get "/tags/filter/search.json",
            params: {
              q: "",
              categoryId: category.id,
              filterForInput: true,
            }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"].map { |t| t["name"] }).to contain_exactly(
          tag1.name,
          tag2.name,
        )
        expect(response.parsed_body["required_tag_group"]).to eq(
          { "name" => tag_group.name, "min_count" => crtg.min_count },
        )

        get "/tags/filter/search.json",
            params: {
              q: "",
              categoryId: category.id,
              filterForInput: true,
              selected_tags: [tag1.name],
            }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"].map { |t| t["name"] }).to contain_exactly(tag2.name)
        expect(response.parsed_body["required_tag_group"]).to eq(nil)

        get "/tags/filter/search.json",
            params: {
              q: "",
              categoryId: category.id,
              filterForInput: true,
              selected_tag_ids: [tag1.id],
            }
        expect(response.status).to eq(200)
        expect(response.parsed_body["results"].map { |t| t["name"] }).to contain_exactly(tag2.name)
      end

      context "with content_localization_enabled" do
        fab!(:japanese_user) { Fabricate(:user, locale: "ja") }

        before do
          SiteSetting.content_localization_enabled = true
          sign_in(japanese_user)
        end

        it "returns localized tag names when localizations exist for the user's locale" do
          tag = Fabricate(:tag, name: "strategy", locale: "en", public_topic_count: 1)
          Fabricate(:tag_localization, tag: tag, locale: "ja", name: "戦略")

          get "/tags/filter/search.json", params: { q: "strat" }

          expect(response.status).to eq(200)
          expect(response.parsed_body["results"]).to include(
            include("name" => "戦略", "id" => tag.id),
          )
        end

        it "returns original tag names when no localization matches the user's locale" do
          tag = Fabricate(:tag, name: "strategy", locale: "en", public_topic_count: 1)
          Fabricate(:tag_localization, tag: tag, locale: "de", name: "stratégie")

          get "/tags/filter/search.json", params: { q: "strat" }

          expect(response.status).to eq(200)
          expect(response.parsed_body["results"]).to include(
            include("name" => "strategy", "id" => tag.id),
          )
        end
      end
    end
  end

  describe "#destroy" do
    context "with tagging enabled" do
      before { sign_in(admin) }

      context "with an existent tag name" do
        it "deletes the tag" do
          tag = Fabricate(:tag)
          delete "/tag/#{tag.name}.json"
          expect(response.status).to eq(200)
          expect(Tag.where(id: tag.id)).to be_empty
        end
      end

      context "with a nonexistent tag name" do
        it "returns a tag not found message" do
          delete "/tag/doesntexists.json"
          expect(response).not_to be_successful
          expect(response.parsed_body["error_type"]).to eq("not_found")
        end
      end
    end
  end

  describe "#unused" do
    it "fails if you can't manage tags" do
      sign_in(user)
      get "/tags/unused.json"
      expect(response.status).to eq(403)
      delete "/tags/unused.json"
      expect(response.status).to eq(403)
    end

    context "when logged in" do
      before { sign_in(admin) }

      context "with some tags" do
        let!(:tags) do
          [
            Fabricate(
              :tag,
              name: "used_publicly",
              public_topic_count: 2,
              staff_topic_count: 2,
              pm_topic_count: 0,
            ),
            Fabricate(
              :tag,
              name: "used_privately",
              public_topic_count: 0,
              staff_topic_count: 0,
              pm_topic_count: 3,
            ),
            Fabricate(
              :tag,
              name: "used_everywhere",
              public_topic_count: 0,
              staff_topic_count: 0,
              pm_topic_count: 3,
            ),
            Fabricate(
              :tag,
              name: "unused1",
              public_topic_count: 0,
              staff_topic_count: 0,
              pm_topic_count: 0,
            ),
            Fabricate(
              :tag,
              name: "unused2",
              public_topic_count: 0,
              staff_topic_count: 0,
              pm_topic_count: 0,
            ),
          ]
        end

        it "returns the correct unused tags" do
          get "/tags/unused.json"
          expect(response.status).to eq(200)
          expect(response.parsed_body["tags"]).to contain_exactly("unused1", "unused2")
        end

        it "deletes the correct tags" do
          expect { delete "/tags/unused.json" }.to change { Tag.count }.by(-2) &
            change { UserHistory.count }.by(1)
          expect(Tag.pluck(:name)).to contain_exactly(
            "used_publicly",
            "used_privately",
            "used_everywhere",
          )
        end
      end
    end
  end

  describe "#upload_csv" do
    it "requires you to be logged in" do
      post "/tags/upload.json"
      expect(response.status).to eq(403)
    end

    context "while logged in" do
      let(:csv_file) { File.new("#{Rails.root}/spec/fixtures/csv/tags.csv") }
      let(:invalid_csv_file) { File.new("#{Rails.root}/spec/fixtures/csv/tags_invalid.csv") }

      let(:file) { Rack::Test::UploadedFile.new(File.open(csv_file)) }

      let(:invalid_file) { Rack::Test::UploadedFile.new(File.open(invalid_csv_file)) }

      let(:filename) { "tags.csv" }

      it "fails if you can't manage tags" do
        sign_in(user)
        post "/tags/upload.json", params: { file: file, name: filename }
        expect(response.status).to eq(403)
      end

      it "allows staff to bulk upload tags" do
        sign_in(moderator)
        post "/tags/upload.json", params: { file: file, name: filename }
        expect(response.status).to eq(200)
        expect(Tag.pluck(:name)).to contain_exactly(
          "tag1",
          "capitaltag2",
          "spaced-tag",
          "tag3",
          "tag4",
        )
        expect(Tag.find_by_name("tag3").tag_groups.pluck(:name)).to contain_exactly("taggroup1")
        expect(Tag.find_by_name("tag4").tag_groups.pluck(:name)).to contain_exactly("taggroup1")
      end

      it "does not fail if tags already exist" do
        Fabricate(:tag, name: "TAG1")
        Fabricate(:tag_group, name: "TAGGROUP1")

        sign_in(moderator)

        post "/tags/upload.json", params: { file: file, name: filename }

        expect(response.status).to eq(200)
        expect(Tag.pluck(:name)).to contain_exactly(
          "TAG1",
          "capitaltag2",
          "spaced-tag",
          "tag3",
          "tag4",
        )
        expect(Tag.find_by_name("tag3").tag_groups.pluck(:name)).to contain_exactly("TAGGROUP1")
        expect(Tag.find_by_name("tag4").tag_groups.pluck(:name)).to contain_exactly("TAGGROUP1")
      end

      describe "with `SiteSetting.force_lowercase_tags = false" do
        before { SiteSetting.force_lowercase_tags = false }
        it "does not fail if tags already exist" do
          Fabricate(:tag, name: "tag1")
          Fabricate(:tag, name: "CAPITALTAG2")
          Fabricate(:tag, name: "tag3")
          sign_in(moderator)

          post "/tags/upload.json", params: { file: file, name: filename }

          expect(response.status).to eq(200)
          expect(Tag.pluck(:name)).to contain_exactly(
            "tag1",
            "CAPITALTAG2",
            "spaced-tag",
            "tag3",
            "tag4",
          )
        end
      end

      it "fails gracefully with invalid input" do
        sign_in(moderator)

        expect do
          post "/tags/upload.json", params: { file: invalid_file, name: filename }
          expect(response.status).to eq(422)
        end.not_to change { [Tag.count, TagGroup.count] }
      end
    end
  end

  describe "#create_synonyms" do
    fab!(:tag)

    it "fails if not logged in" do
      post "/tag/#{tag.name}/synonyms.json", params: { synonyms: ["synonym1"] }
      expect(response.status).to eq(403)
    end

    it "fails if user not in allowed group" do
      sign_in(user)
      post "/tag/#{tag.name}/synonyms.json", params: { synonyms: ["synonym1"] }
      expect(response.status).to eq(403)
    end

    it "succeeds when user in allowed group" do
      SiteSetting.edit_tags_allowed_groups = "1|2|13"
      sign_in(regular_user)
      post "/tag/#{tag.name}/synonyms.json", params: { synonyms: ["synonym1"] }
      expect(response.status).to eq(200)
    end

    it "returns 404 when tag is hidden and user is not in permitted group" do
      SiteSetting.edit_tags_allowed_groups = "1|2|13"
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name])
      sign_in(regular_user)
      post "/tag/#{tag.name}/synonyms.json", params: { tags: [{ name: "synonym1" }] }
      expect(response.status).to eq(404)
    end

    it "succeeds when tag is hidden and user is admin" do
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [tag.name])
      sign_in(admin)
      post "/tag/#{tag.name}/synonyms.json", params: { tags: [{ name: "synonym1" }] }
      expect(response.status).to eq(200)
    end

    context "when signed in as admin" do
      before { sign_in(admin) }

      it "can make an existing tag a synonym using tags array with id" do
        tag2 = Fabricate(:tag)
        expect {
          post "/tag/#{tag.name}/synonyms.json",
               params: {
                 tags: [{ id: tag2.id, name: tag2.name }],
               }
        }.to_not change { Tag.count }
        expect(response.status).to eq(200)
        expect(tag2.reload.target_tag).to eq(tag)
      end

      it "can create new tags using tags array without id" do
        expect {
          post "/tag/#{tag.name}/synonyms.json", params: { tags: [{ name: "new-synonym" }] }
        }.to change { Tag.count }.by(1)
        expect(response.status).to eq(200)
        expect(Tag.find_by_name("new-synonym")&.target_tag).to eq(tag)
      end

      it "can use both existing and new tags together in tags array" do
        tag2 = Fabricate(:tag)
        expect {
          post "/tag/#{tag.name}/synonyms.json",
               params: {
                 tags: [{ id: tag2.id, name: tag2.name }, { name: "new-synonym" }],
               }
        }.to change { Tag.count }.by(1)
        expect(response.status).to eq(200)
        expect(tag2.reload.target_tag).to eq(tag)
        expect(Tag.find_by_name("new-synonym")&.target_tag).to eq(tag)
      end

      it "can make a tag a synonym of another tag (deprecated synonyms param)" do
        tag2 = Fabricate(:tag)
        expect {
          post "/tag/#{tag.name}/synonyms.json", params: { synonyms: [tag2.name] }
        }.to_not change { Tag.count }
        expect(response.status).to eq(200)
        expect(tag2.reload.target_tag).to eq(tag)
      end

      it "can create new tags at the same time (deprecated synonyms param)" do
        expect {
          post "/tag/#{tag.name}/synonyms.json", params: { synonyms: ["synonym"] }
        }.to change { Tag.count }.by(1)
        expect(response.status).to eq(200)
        expect(Tag.find_by_name("synonym")&.target_tag).to eq(tag)
      end

      it "can return errors" do
        _tag2 = Fabricate(:tag, target_tag: tag)
        tag3 = Fabricate(:tag)
        post "/tag/#{tag3.name}/synonyms.json", params: { tags: [{ id: tag.id, name: tag.name }] }
        expect(response.status).to eq(200)
        expect(response.parsed_body["failed"]).to be_present
        expect(response.parsed_body.dig("failed_tags", tag.name)).to be_present
      end
    end
  end

  describe "#destroy_synonym" do
    subject(:destroy_synonym) { delete("/tag/#{tag.id}/synonyms/#{synonym.id}.json") }

    fab!(:tag)
    fab!(:synonym) { Fabricate(:tag, target_tag: tag, name: "synonym") }

    it "fails if not logged in" do
      destroy_synonym
      expect(response.status).to eq(403)
    end

    it "fails if not staff user" do
      sign_in(user)
      destroy_synonym
      expect(response.status).to eq(403)
    end

    it "fails when synonym is hidden and user is not in permitted group" do
      SiteSetting.edit_tags_allowed_groups = "1|2|3|13"
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [synonym.name])
      sign_in(regular_user)
      destroy_synonym
      expect(response.status).to eq(403)
    end

    context "when signed in as admin" do
      before { sign_in(admin) }

      it "can remove a synonym from a tag" do
        synonym2 = Fabricate(:tag, target_tag: tag, name: "synonym2")
        expect { destroy_synonym }.to_not change { Tag.count }
        expect_same_tag_names(tag.reload.synonyms, [synonym2])
        expect(synonym.reload).to_not be_synonym
      end

      it "returns error if tag isn't a synonym" do
        other_tag = Fabricate(:tag)
        delete "/tag/#{other_tag.id}/synonyms/#{synonym.id}.json"
        expect(response.status).to eq(400)
        expect_same_tag_names(tag.reload.synonyms, [synonym])
      end

      it "returns error if synonym not found" do
        other_tag = Fabricate(:tag)
        delete "/tag/#{other_tag.id}/synonyms/-1.json"
        expect(response.status).to eq(404)
        expect_same_tag_names(tag.reload.synonyms, [synonym])
      end
    end
  end

  describe "#update_notifications" do
    fab!(:tag)

    before { sign_in(user) }

    it "returns 404 when tag is not found" do
      put "/tag/someinvalidtagname/notifications.json"

      expect(response.status).to eq(404)
    end

    it "updates the notification level of a tag for a user" do
      tag_user = TagUser.change(user.id, tag.id, NotificationLevels.all[:muted])

      put "/tag/#{tag.name}/notifications.json",
          params: {
            tag_notification: {
              notification_level: NotificationLevels.all[:tracking],
            },
          }

      expect(response.status).to eq(200)
      expect(response.parsed_body["watched_tags"]).to eq([])
      expect(response.parsed_body["watching_first_post_tags"]).to eq([])
      expect(response.parsed_body["tracked_tags"]).to eq([{ "id" => tag.id, "name" => tag.name }])
      expect(response.parsed_body["muted_tags"]).to eq([])
      expect(response.parsed_body["regular_tags"]).to eq([])

      expect(tag_user.reload.notification_level).to eq(NotificationLevels.all[:tracking])
    end

    it "sets the notification level of a tag for a user" do
      expect do
        put "/tag/#{tag.name}/notifications.json",
            params: {
              tag_notification: {
                notification_level: NotificationLevels.all[:muted],
              },
            }

        expect(response.status).to eq(200)

        expect(response.parsed_body["watched_tags"]).to eq([])
        expect(response.parsed_body["watching_first_post_tags"]).to eq([])
        expect(response.parsed_body["tracked_tags"]).to eq([])
        expect(response.parsed_body["muted_tags"]).to eq([{ "id" => tag.id, "name" => tag.name }])
        expect(response.parsed_body["regular_tags"]).to eq([])
      end.to change { user.tag_users.count }.by(1)

      tag_user = user.tag_users.last

      expect(tag_user.notification_level).to eq(NotificationLevels.all[:muted])
    end
  end

  describe "#list" do
    fab!(:tag3) { Fabricate(:tag, name: "tag3") }
    fab!(:tag2) { Fabricate(:tag, name: "tag2") }
    fab!(:tag1) { Fabricate(:tag, name: "tag") }

    fab!(:staff_only_tag) { Fabricate(:tag, name: "tag4") }

    let!(:staff_tag_group) do
      Fabricate(:tag_group, permissions: { "staff" => 1 }, tag_names: [staff_only_tag.name])
    end

    it "should return 403 for an anonymous user" do
      get "/tags/list.json"

      expect(response.status).to eq(403)
    end

    it "should return 404 when tagging is disabled" do
      SiteSetting.tagging_enabled = false

      sign_in(user)

      get "/tags/list.json"

      expect(response.status).to eq(404)
    end

    it "should only return tags that are visible to the user for non admin users" do
      stub_const(TagsController, "LIST_LIMIT", 2) do
        sign_in(user)

        get "/tags/list.json"

        expect(response.status).to eq(200)

        expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq(
          [tag1.name, tag2.name],
        )

        expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(3)
        expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
          "/tags/list.json?offset=1",
        )

        get response.parsed_body["meta"]["load_more_list_tags"]

        expect(response.status).to eq(200)
        expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq([tag3.name])
        expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(3)

        expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
          "/tags/list.json?offset=2",
        )
      end
    end

    it "should return all tags for admin users" do
      stub_const(TagsController, "LIST_LIMIT", 2) do
        sign_in(admin)

        get "/tags/list.json"

        expect(response.status).to eq(200)

        expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq(
          [tag1.name, tag2.name],
        )

        expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(4)

        expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
          "/tags/list.json?offset=1",
        )

        get response.parsed_body["meta"]["load_more_list_tags"]

        expect(response.status).to eq(200)

        expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq(
          [tag3.name, staff_only_tag.name],
        )

        expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(4)

        expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
          "/tags/list.json?offset=2",
        )
      end
    end

    it "accepts a `filter` param and filters the tags by tag name" do
      sign_in(user)

      get "/tags/list.json", params: { filter: "3" }

      expect(response.status).to eq(200)

      expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq([tag3.name])
      expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(1)

      expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
        "/tags/list.json?offset=1&filter=3",
      )
    end

    it "accepts a `only_tags` param and filters the tags by the given tags" do
      sign_in(user)

      get "/tags/list.json", params: { only_tags: "#{tag1.name},#{tag3.name}" }

      expect(response.status).to eq(200)

      expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq(
        [tag1.name, tag3.name],
      )

      expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(2)

      expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
        "/tags/list.json?offset=1&only_tags=#{tag1.name}%2C#{tag3.name}",
      )
    end

    it "accepts a `exclude_tags` params and filters tags excluding the given tags" do
      sign_in(user)

      get "/tags/list.json", params: { exclude_tags: "#{tag1.name},#{tag3.name}" }

      expect(response.status).to eq(200)

      expect(response.parsed_body["list_tags"].map { |tag| tag["name"] }).to eq([tag2.name])

      expect(response.parsed_body["meta"]["total_rows_list_tags"]).to eq(1)

      expect(response.parsed_body["meta"]["load_more_list_tags"]).to eq(
        "/tags/list.json?offset=1&exclude_tags=#{tag1.name}%2C#{tag3.name}",
      )
    end
  end
end
