# Copyright (C) 2012-2025 Zammad Foundation, https://zammad-foundation.org/

require 'rails_helper'

RSpec.describe 'Search', type: :request do

  let(:group) { create(:group) }
  let!(:admin) do
    create(:admin, groups: [Group.lookup(name: 'Users'), group])
  end
  let!(:agent) do
    create(:agent, firstname: 'Search 1234', groups: [Group.lookup(name: 'Users'), group])
  end
  let!(:customer) do
    create(:customer)
  end
  let!(:organization1) do
    create(:organization, name: 'Rest Org')
  end
  let!(:organization2) do
    create(:organization, name: 'Rest Org #2')
  end
  let!(:organization3) do
    create(:organization, name: 'Rest Org #3')
  end
  let!(:organization4) do
    create(:organization, name: 'Tes.t. Org')
  end
  let!(:organization5) do
    create(:organization, name: 'ABC_D Org')
  end
  let!(:customer2) do
    create(:customer, organization: organization1)
  end
  let!(:customer3) do
    create(:customer, organization: organization1)
  end
  let!(:ticket1) do
    create(:ticket, title: 'test 1234-1', customer: customer, group: group)
  end
  let!(:ticket2) do
    create(:ticket, title: 'test 1234-2', customer: customer2, group: group)
  end
  let!(:ticket3) do
    create(:ticket, title: 'test 1234-2', customer: customer3, group: group)
  end
  let!(:article1) do
    create(:ticket_article, ticket_id: ticket1.id)
  end
  let!(:article2) do
    create(:ticket_article, ticket_id: ticket2.id)
  end
  let!(:article3) do
    create(:ticket_article, ticket_id: ticket3.id)
  end

  describe 'request handling', performs_jobs: true, searchindex: true do
    before do
      searchindex_model_reload([Ticket, User, Organization])
    end

    it 'does settings index with nobody' do
      params = {
        query: 'test 1234',
        limit: 2,
      }

      post '/api/v1/search/ticket', params: params, as: :json
      expect(response).to have_http_status(:forbidden)
      expect(json_response).to be_a(Hash)
      expect(json_response).not_to be_blank
      expect(json_response['error']).to eq('Authentication required')

      post '/api/v1/search/user', params: params, as: :json
      expect(response).to have_http_status(:forbidden)
      expect(json_response).to be_a(Hash)
      expect(json_response).not_to be_blank
      expect(json_response['error']).to eq('Authentication required')

      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:forbidden)
      expect(json_response).to be_a(Hash)
      expect(json_response).not_to be_blank
      expect(json_response['error']).to eq('Authentication required')
    end

    it 'does settings index with admin' do
      params = {
        query: '1234*',
        limit: 1,
      }
      authenticated_as(admin)
      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('User')
      expect(json_response['result'][1]['id']).to eq(agent.id)
      expect(json_response['result'][2]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]['type']).to eq('Ticket')
      expect(json_response['result'][2]['id']).to eq(ticket1.id)
      expect(json_response['result'][3]['type']).to eq('User')
      expect(json_response['result'][3]['id']).to eq(agent.id)
      expect(json_response['result'][4]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/ticket', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]['type']).to eq('Ticket')
      expect(json_response['result'][2]['id']).to eq(ticket1.id)
      expect(json_response['result'][3]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/user', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response['result'][0]['type']).to eq('User')
      expect(json_response['result'][0]['id']).to eq(agent.id)
      expect(json_response['result'][1]).to be_falsey
    end

    it 'does settings index with agent' do
      params = {
        query: '1234*',
        limit: 1,
      }

      authenticated_as(agent)
      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('User')
      expect(json_response['result'][1]['id']).to eq(agent.id)
      expect(json_response['result'][2]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]['type']).to eq('Ticket')
      expect(json_response['result'][2]['id']).to eq(ticket1.id)
      expect(json_response['result'][3]['type']).to eq('User')
      expect(json_response['result'][3]['id']).to eq(agent.id)
      expect(json_response['result'][4]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/ticket', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]['type']).to eq('Ticket')
      expect(json_response['result'][2]['id']).to eq(ticket1.id)
      expect(json_response['result'][3]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/user', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response['result'][0]['type']).to eq('User')
      expect(json_response['result'][0]['id']).to eq(agent.id)
      expect(json_response['result'][1]).to be_falsey
    end

    it 'does settings index with customer 1' do
      params = {
        query: '1234*',
        limit: 10,
      }

      authenticated_as(customer)
      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket1.id)
      expect(json_response['result'][1]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/ticket', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket1.id)
      expect(json_response['result'][1]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/user', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response['result'][0]).to be_falsey
    end

    it 'does settings index with customer 2' do
      params = {
        query: '1234*',
        limit: 10,
      }

      authenticated_as(customer2)
      post '/api/v1/search', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/ticket', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['result'][0]['type']).to eq('Ticket')
      expect(json_response['result'][0]['id']).to eq(ticket3.id)
      expect(json_response['result'][1]['type']).to eq('Ticket')
      expect(json_response['result'][1]['id']).to eq(ticket2.id)
      expect(json_response['result'][2]).to be_falsey

      params = {
        query: '1234*',
        limit: 10,
      }

      post '/api/v1/search/user', params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response['result'][0]).to be_falsey
    end

    # Verify fix for Github issue #2058 - Autocomplete hangs on dot in the new user form
    it 'does searching for organization with a dot in its name' do
      authenticated_as(agent)
      get '/api/v1/search/organization?query=tes.', as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response['result'].size).to eq(1)
      expect(json_response['result'][0]['type']).to eq('Organization')
      target_id = json_response['result'][0]['id']
      expect(json_response['assets']['Organization'][target_id.to_s]['name']).to eq('Tes.t. Org')
    end

    # Search query H& should correctly match H&M
    it 'does searching for organization with _ in its name' do
      authenticated_as(agent)
      get '/api/v1/search/organization?query=abc_', as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response['result'].size).to eq(1)
      expect(json_response['result'][0]['type']).to eq('Organization')
      target_id = json_response['result'][0]['id']
      expect(json_response['assets']['Organization'][target_id.to_s]['name']).to eq('ABC_D Org')
    end

    it 'does find the ticket by group name even if the group name changes' do
      authenticated_as(agent)
      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && group.name:ultrasupport" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket']).to be_falsey
      expect(group).not_to eq('ultrasupport')

      group.update(name: 'ultrasupport')
      perform_enqueued_jobs
      SearchIndexBackend.refresh

      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && group.name:ultrasupport" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket'][ticket1.id.to_s]).to be_truthy
    end

    it 'does find the ticket by state name even if the state name changes' do
      authenticated_as(agent)
      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && state.name:ultrastate" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket']).to be_falsey
      expect(ticket1.state.name).not_to eq('ultrastate')

      ticket1.state.update(name: 'ultrastate')
      perform_enqueued_jobs
      SearchIndexBackend.refresh

      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && state.name:ultrastate" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket'][ticket1.id.to_s]).to be_truthy
    end

    it 'does find the ticket by priority name even if the priority name changes' do
      authenticated_as(agent)
      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && priority.name:ultrapriority" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket']).to be_falsey
      expect(ticket1.priority.name).not_to eq('ultrapriority')

      ticket1.priority.update(name: 'ultrapriority')
      perform_enqueued_jobs
      SearchIndexBackend.refresh

      post '/api/v1/search/Ticket', params: { query: "number:#{ticket1.number} && priority.name:ultrapriority" }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket'][ticket1.id.to_s]).to be_truthy
    end

    it 'does find the ticket by the checklist name', current_user_id: 1 do
      authenticated_as(agent)

      ticket1.create_checklist! name: 'chcklst name'
      perform_enqueued_jobs
      SearchIndexBackend.refresh

      post '/api/v1/search/Ticket', params: { query: 'chcklst' }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket'][ticket1.id.to_s]).to be_truthy
    end

    it 'does find the ticket by a checklist entry', current_user_id: 1 do
      authenticated_as(agent)

      ticket1.create_checklist!
      ticket1.checklist.items.create! text: 'checklist entry'
      perform_enqueued_jobs
      SearchIndexBackend.refresh

      post '/api/v1/search/Ticket', params: { query: 'checklist entry' }, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_a(Hash)
      expect(json_response).to be_truthy
      expect(json_response['assets']['Ticket'][ticket1.id.to_s]).to be_truthy
    end
  end

  describe 'Assign user to multiple organizations #1573' do
    shared_examples 'search for organization ids' do
      context 'when customer with multi organizations', authenticated_as: :customer do
        context 'with multi organizations' do
          let(:customer) { create(:customer, organization: organizations[0], organizations: organizations[1..2]) }
          let(:organizations) { create_list(:organization, 5) }

          it 'does not return organizations which are not allowed' do
            params = {
              query: 'TestOrganization',
              limit: 10,
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end

          it 'does not return organizations which are not allowed when overwritten' do
            params = {
              query: 'TestOrganization',
              limit: 10,
              ids:   organizations.map(&:id)
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end
        end

        context 'with single organization' do
          let(:customer) { create(:customer, organization: organizations[0]) }
          let(:organizations) { create_list(:organization, 5) }

          it 'does not return organizations which are not allowed' do
            params = {
              query: 'TestOrganization',
              limit: 10,
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end

          it 'does not return organizations which are not allowed when overwritten' do
            params = {
              query: 'TestOrganization',
              limit: 10,
              ids:   organizations.map(&:id)
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end
        end

        context 'with no organization' do
          let(:customer) do
            organizations
            create(:customer)
          end
          let(:organizations) { create_list(:organization, 5) }

          it 'does not return organizations which are not allowed' do
            params = {
              query: 'TestOrganization',
              limit: 10,
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).not_to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end

          it 'does not return organizations which are not allowed when overwritten' do
            params = {
              query: 'TestOrganization',
              limit: 10,
              ids:   organizations.map(&:id)
            }
            post '/api/v1/search/Organization', params: params, as: :json
            expect(json_response['result']).not_to include({ 'id' => organizations[0].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[1].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[2].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[3].id, 'type' => 'Organization' })
            expect(json_response['result']).not_to include({ 'id' => organizations[4].id, 'type' => 'Organization' })
          end
        end
      end

      it 'does return all organizations' do
        params = {
          query: 'Rest',
          limit: 10,
        }
        authenticated_as(admin)
        post '/api/v1/search/Organization', params: params, as: :json
        expect(json_response['result']).to include({ 'id' => organization1.id, 'type' => 'Organization' })
        expect(json_response['result']).to include({ 'id' => organization2.id, 'type' => 'Organization' })
        expect(json_response['result']).to include({ 'id' => organization3.id, 'type' => 'Organization' })
      end

      it 'does return organization specific ids' do
        params = {
          query: 'Rest',
          ids:   [organization1.id],
          limit: 10,
        }
        authenticated_as(admin)
        post '/api/v1/search/Organization', params: params, as: :json
        expect(json_response['result']).to include({ 'id' => organization1.id, 'type' => 'Organization' })
        expect(json_response['result']).not_to include({ 'id' => organization2.id, 'type' => 'Organization' })
        expect(json_response['result']).not_to include({ 'id' => organization3.id, 'type' => 'Organization' })
      end
    end

    context 'with elasticsearch', searchindex: true do
      before do
        searchindex_model_reload([Ticket, User, Organization])
      end

      include_examples 'search for organization ids'
    end

    context 'with db only', searchindex: false do
      before do
        Setting.set('es_url', nil)
      end

      include_examples 'search for organization ids'
    end
  end
end