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

require 'rails_helper'

RSpec.describe 'ObjectManager Attributes', type: :request do

  let(:admin) do
    create(:admin)
  end

  describe 'request handling' do

    it 'does add new ticket text object' do
      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: {}, as: :json

      # token based on headers
      params = {
        name:        'test1',
        object:      'Ticket',
        display:     'Test 1',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   'test',
          type:      'text',
          maxlength: 120
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
        id:          'c-196'
      }

      post '/api/v1/object_manager_attributes', params: params, as: :json
      expect(response).to have_http_status(:created)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['null']).to be_truthy
      expect(json_response['data_option']['null']).to be(true)
      expect(json_response['name']).to eq('test1')
    end

    it 'does add new ticket text object - no default' do
      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: {}, as: :json

      # token based on headers
      params = {
        name:        'test2',
        object:      'Ticket',
        display:     'Test 2',
        active:      true,
        data_type:   'input',
        data_option: {
          type:      'text',
          maxlength: 120
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
        id:          'c-196'
      }

      post '/api/v1/object_manager_attributes', params: params, as: :json
      expect(response).to have_http_status(:created)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['null']).to be_truthy
      expect(json_response['data_option']['null']).to be(true)
      expect(json_response['name']).to eq('test2')
    end

    it 'does update ticket text object', db_strategy: :reset do

      # add a new object
      object = create(:object_manager_attribute_text)

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      authenticated_as(admin)
      post "/api/v1/object_manager_attributes/#{object.id}", params: {}, as: :json

      # parameters for updating
      params = {
        name:        object.name,
        object:      'Ticket',
        display:     'Test 4',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   'test',
          type:      'text',
          maxlength: 120
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
        id:          'c-196'
      }

      # update the object
      put "/api/v1/object_manager_attributes/#{object.id}", params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['null']).to be_truthy
      expect(json_response['name']).to eq(object.name)
      expect(json_response['display']).to eq('Test 4')
    end

    it 'does add new ticket boolean object' do
      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: {}, as: :json

      # token based on headers
      params = {
        active:      true,
        data_option: {
          options: {
            false: 'no',
            true:  'yes'
          }
        },
        data_type:   'boolean',
        display:     'Boolean 2',
        id:          'c-200',
        name:        'bool2',
        object:      'Ticket',
        screens:     {
          create_middle: {
            'ticket.agent'    => {
              item_class: 'column',
              shown:      true
            },
            'ticket.customer' => {
              item_class: 'column',
              shown:      true
            }
          },
          edit:          {
            'ticket.agent'    => {
              shown: true
            },
            'ticket.customer' => {
              shown: true
            }
          }
        }
      }

      post '/api/v1/object_manager_attributes', params: params, as: :json
      expect(response).to have_http_status(:created)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['null']).to be_truthy
      expect(json_response['data_option']['null']).to be(true)
      expect(json_response['name']).to eq('bool2')
    end

    it 'does add new user select object' do
      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: {}, as: :json

      # token based on headers
      params = {
        active:      true,
        data_option: {
          options: {
            key1: 'foo'
          }
        },
        data_type:   'select',
        display:     'Test 5',
        id:          'c-204',
        name:        'test5',
        object:      'User',
        screens:     {
          create: {
            'admin.user'      => {
              shown: true
            },
            'ticket.agent'    => {
              shown: true
            },
            'ticket.customer' => {
              shown: true
            }
          },
          edit:   {
            'admin.user'   => {
              shown: true
            },
            'ticket.agent' => {
              shown: true
            }
          },
          view:   {
            'admin.user'      => {
              shown: true
            },
            'ticket.agent'    => {
              shown: true
            },
            'ticket.customer' => {
              shown: true
            }
          }
        }
      }

      post '/api/v1/object_manager_attributes', params: params, as: :json
      expect(response).to have_http_status(:created)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['null']).to be_truthy
      expect(json_response['data_option']['null']).to be(true)
      expect(json_response['name']).to eq('test5')
    end

    it 'does update user select object', authenticated_as: -> { admin }, db_strategy: :reset do
      # add a new object
      object = create(:object_manager_attribute_text, object_name: 'User')

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      post "/api/v1/object_manager_attributes/#{object.id}", params: {}, as: :json

      # parameters for updating
      params = {
        active:      true,
        data_option: {
          options: {
            key1: 'foo',
            key2: 'bar'
          }
        },
        data_type:   'select',
        display:     'Test 7',
        id:          'c-204',
        name:        object.name,
        object:      'User',
        screens:     {
          create: {
            'admin.user'      => {
              shown: true
            },
            'ticket.agent'    => {
              shown: true
            },
            'ticket.customer' => {
              shown: true
            }
          },
          edit:   {
            'admin.user'   => {
              shown: true
            },
            'ticket.agent' => {
              shown: true
            }
          },
          view:   {
            'admin.user'      => {
              shown: true
            },
            'ticket.agent'    => {
              shown: true
            },
            'ticket.customer' => {
              shown: true
            }
          }
        }
      }

      # update the object
      put "/api/v1/object_manager_attributes/#{object.id}", params: params, as: :json
      expect(response).to have_http_status(:ok)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['options']).to be_truthy
      expect(json_response['name']).to eq(object.name)
      expect(json_response['display']).to eq('Test 7')
    end

    it 'does converts string to boolean for default value for boolean data type with true (01)', db_strategy: :reset do
      params = {
        name:        "customerdescription#{SecureRandom.uuid.tr('-', '_')}",
        object:      'Ticket',
        display:     "custom description#{SecureRandom.uuid.tr('-', '_')}",
        active:      true,
        data_type:   'boolean',
        data_option: {
          options: {
            true:  '',
            false: '',
          },
          default: 'true',
          screens: {
            create_middle: {
              'ticket.customer': {
                shown:      true,
                item_class: 'column'
              },
              'ticket.agent':    {
                shown:      true,
                item_class: 'column'
              }
            },
            edit:          {
              'ticket.customer': {
                shown: true
              },
              'ticket.agent':    {
                shown: true
              }
            }
          }
        },
        id:          'c-201'
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      expect(response).to have_http_status(:created) # created

      expect(json_response).to be_truthy
      expect(json_response['data_option']['default']).to be_truthy
      expect(json_response['data_option']['default']).to be(true)
      expect(json_response['data_type']).to eq('boolean')
    end

    it 'does converts string to boolean for default value for boolean data type with false (02)', db_strategy: :reset do
      params = {
        name:        "customerdescription_#{SecureRandom.uuid.tr('-', '_')}",
        object:      'Ticket',
        display:     "custom description #{SecureRandom.uuid.tr('-', '_')}",
        active:      true,
        data_type:   'boolean',
        data_option: {
          options: {
            true:  '',
            false: '',
          },
          default: 'false',
          screens: {
            create_middle: {
              'ticket.customer': {
                shown:      true,
                item_class: 'column'
              },
              'ticket.agent':    {
                shown:      true,
                item_class: 'column'
              }
            },
            edit:          {
              'ticket.customer': {
                shown: true
              },
              'ticket.agent':    {
                shown: true
              }
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      expect(response).to have_http_status(:created) # created

      expect(json_response).to be_truthy
      expect(json_response['data_option']['default']).to be_falsey
      expect(json_response['data_option']['default']).to be(false)
      expect(json_response['data_type']).to eq('boolean')
    end

    it 'does ticket attributes cannot be removed when it is referenced by an overview (03)', db_strategy: :reset do

      # 1. create a new ticket attribute and execute migration
      ObjectManager::Attribute.migration_execute

      params = {
        name:        'test_attribute_referenced_by_an_overview',
        object:      'Ticket',
        display:     'Test Attribute',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   '',
          type:      'text',
          maxlength: 120,
          null:      true,
          options:   {},
          relation:  ''
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      # 2. create an overview that uses the attribute
      params = {
        name:      'test_overview',
        roles:     Role.where(name: 'Agent').pluck(:name),
        condition: {
          'ticket.state_id':                                 {
            operator: 'is',
            value:    Ticket::State.pluck(:id),
          },
          'ticket.test_attribute_referenced_by_an_overview': {
            operator: 'contains',
            value:    'DUMMY'
          },
        },
        order:     {
          by:        'created_at',
          direction: 'DESC',
        },
        view:      {
          d:                 %w[title customer state created_at],
          s:                 %w[number title customer state created_at],
          m:                 %w[number title customer state created_at],
          view_mode_default: 's',
        },
        user_ids:  [ '1' ],
      }

      if Overview.where('name like ?', '%test%').empty?
        post '/api/v1/overviews', params: params, as: :json
        expect(response).to have_http_status(:created)
        expect(Hash).to eq(json_response.class)
        expect(json_response['name']).to eq('test_overview')
      end

      # 3. attempt to delete the ticket attribute
      get '/api/v1/object_manager_attributes', as: :json
      expect(response).to have_http_status(:ok)
      target_attribute = json_response.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'Ticket' }
      expect(target_attribute.size).to eq(1)
      target_id = target_attribute[0]['id']

      delete "/api/v1/object_manager_attributes/#{target_id}", as: :json
      expect(response).to have_http_status(:unprocessable_entity)
      expect(response.body).to include('Overview')
      expect(response.body).to include('test_overview')
      expect(response.body).to include('cannot be deleted!')
    end

    it 'does ticket attributes cannot be removed when it is referenced by a trigger (04)', db_strategy: :reset do

      # 1. create a new ticket attribute and execute migration
      ObjectManager::Attribute.migration_execute

      params = {
        name:        'test_attribute_referenced_by_a_trigger',
        object:      'Ticket',
        display:     'Test Attribute',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   '',
          type:      'text',
          maxlength: 120,
          null:      true,
          options:   {},
          relation:  ''
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      # 2. create an trigger that uses the attribute
      params = {
        name:      'test_trigger',
        condition: {
          'ticket.test_attribute_referenced_by_a_trigger': {
            operator: 'contains',
            value:    'DUMMY'
          }
        },
        perform:   {
          'ticket.state_id': {
            value: '2'
          }
        },
        active:    true,
        id:        'c-3'
      }

      if Trigger.where('name like ?', '%test%').empty?
        post '/api/v1/triggers', params: params, as: :json
        expect(response).to have_http_status(:created)
        expect(Hash).to eq(json_response.class)
        expect(json_response['name']).to eq('test_trigger')
      end

      # 3. attempt to delete the ticket attribute
      get '/api/v1/object_manager_attributes', as: :json
      expect(response).to have_http_status(:ok)
      target_attribute = json_response.select { |x| x['name'] == 'test_attribute_referenced_by_a_trigger' && x['object'] == 'Ticket' }
      expect(target_attribute.size).to eq(1)
      target_id = target_attribute[0]['id']

      delete "/api/v1/object_manager_attributes/#{target_id}", as: :json
      expect(response).to have_http_status(:unprocessable_entity)
      expect(response.body).to include('Trigger')
      expect(response.body).to include('test_trigger')
      expect(response.body).to include('cannot be deleted!')
    end

    it 'does ticket attributes cannot be removed when it is referenced by a scheduler (05)', db_strategy: :reset do

      # 1. create a new ticket attribute and execute migration
      ObjectManager::Attribute.migration_execute

      params = {
        name:        'test_attribute_referenced_by_a_scheduler',
        object:      'Ticket',
        display:     'Test Attribute',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   '',
          type:      'text',
          maxlength: 120,
          null:      true,
          options:   {},
          relation:  ''
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      # 2. create a scheduler that uses the attribute
      params = {
        name:                 'test_scheduler',
        object:               'Ticket',
        timeplan:             {
          days:    {
            Mon: true,
            Tue: false,
            Wed: false,
            Thu: false,
            Fri: false,
            Sat: false,
            Sun: false
          },
          hours:   {
            '0':  true,
            '1':  false,
            '2':  false,
            '3':  false,
            '4':  false,
            '5':  false,
            '6':  false,
            '7':  false,
            '8':  false,
            '9':  false,
            '10': false,
            '11': false,
            '12': false,
            '13': false,
            '14': false,
            '15': false,
            '16': false,
            '17': false,
            '18': false,
            '19': false,
            '20': false,
            '21': false,
            '22': false,
            '23': false
          },
          minutes: {
            '0':  true,
            '10': false,
            '20': false,
            '30': false,
            '40': false,
            '50': false
          }
        },
        condition:            {
          'ticket.test_attribute_referenced_by_a_scheduler': {
            operator: 'contains',
            value:    'DUMMY'
          }
        },
        perform:              {
          'ticket.state_id': {
            value: '2'
          }
        },
        disable_notification: true,
        note:                 '',
        active:               true,
        id:                   'c-0'
      }

      if Job.where('name like ?', '%test%').empty?
        post '/api/v1/jobs', params: params, as: :json
        expect(response).to have_http_status(:created)
        expect(Hash).to eq(json_response.class)
        expect(json_response['name']).to eq('test_scheduler')
      end

      # 3. attempt to delete the ticket attribute
      get '/api/v1/object_manager_attributes', as: :json
      expect(response).to have_http_status(:ok)
      target_attribute = json_response.select { |x| x['name'] == 'test_attribute_referenced_by_a_scheduler' && x['object'] == 'Ticket' }
      expect(target_attribute.size).to eq(1)
      target_id = target_attribute[0]['id']

      delete "/api/v1/object_manager_attributes/#{target_id}", as: :json
      expect(response).to have_http_status(:unprocessable_entity)
      expect(response.body).to include('Job')
      expect(response.body).to include('test_scheduler')
      expect(response.body).to include('cannot be deleted!')
    end

    it 'does ticket attributes can be removed when it is referenced by an overview but by user object (06)', db_strategy: :reset do

      # 1. create a new ticket attribute and execute migration
      ObjectManager::Attribute.migration_execute

      params = {
        name:        'test_attribute_referenced_by_an_overview',
        object:      'Ticket',
        display:     'Test Attribute',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   '',
          type:      'text',
          maxlength: 120,
          null:      true,
          options:   {},
          relation:  ''
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      params = {
        name:        'test_attribute_referenced_by_an_overview',
        object:      'User',
        display:     'Test Attribute',
        active:      true,
        data_type:   'input',
        data_option: {
          default:   '',
          type:      'text',
          maxlength: 120,
          null:      true,
          options:   {},
          relation:  ''
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          }
        },
      }

      post '/api/v1/object_manager_attributes', params: params, as: :json

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      # 2. create an overview that uses the attribute
      params = {
        name:      'test_overview',
        roles:     Role.where(name: 'Agent').pluck(:name),
        condition: {
          'ticket.state_id':                                 {
            operator: 'is',
            value:    Ticket::State.pluck(:id),
          },
          'ticket.test_attribute_referenced_by_an_overview': {
            operator: 'contains',
            value:    'DUMMY'
          },
        },
        order:     {
          by:        'created_at',
          direction: 'DESC',
        },
        view:      {
          d:                 %w[title customer state created_at],
          s:                 %w[number title customer state created_at],
          m:                 %w[number title customer state created_at],
          view_mode_default: 's',
        },
        user_ids:  [ '1' ],
      }

      if Overview.where('name like ?', '%test%').empty?
        post '/api/v1/overviews', params: params, as: :json
        expect(response).to have_http_status(:created)
        expect(Hash).to eq(json_response.class)
        expect(json_response['name']).to eq('test_overview')
      end

      # 3. attempt to delete the ticket attribute
      get '/api/v1/object_manager_attributes', as: :json
      expect(response).to have_http_status(:ok)
      all_json_response = json_response

      target_attribute = all_json_response.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'User' }
      expect(target_attribute.size).to eq(1)
      target_id = target_attribute[0]['id']

      delete "/api/v1/object_manager_attributes/#{target_id}", as: :json
      expect(response).to have_http_status(:ok)

      target_attribute = all_json_response.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'Ticket' }
      expect(target_attribute.size).to eq(1)
      target_id = target_attribute[0]['id']

      delete "/api/v1/object_manager_attributes/#{target_id}", as: :json
      expect(response).to have_http_status(:unprocessable_entity)
      expect(response.body).to include('Overview')
      expect(response.body).to include('test_overview')
      expect(response.body).to include('cannot be deleted!')

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)
    end

    it 'does verify if attribute type can not be changed (07)', db_strategy: :reset do

      params = {
        name:        "customerdescription_#{SecureRandom.uuid.tr('-', '_')}",
        object:      'Ticket',
        display:     "custom description #{SecureRandom.uuid.tr('-', '_')}",
        active:      true,
        data_type:   'boolean',
        data_option: {
          options: {
            true:  '',
            false: '',
          },
          default: 'false',
          screens: {
            create_middle: {
              'ticket.customer': {
                shown:      true,
                item_class: 'column'
              },
              'ticket.agent':    {
                shown:      true,
                item_class: 'column'
              }
            },
            edit:          {
              'ticket.customer': {
                shown: true
              },
              'ticket.agent':    {
                shown: true
              }
            }
          }
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      expect(response).to have_http_status(:created) # created

      expect(json_response).to be_truthy
      expect(json_response['data_option']['default']).to be_falsey
      expect(json_response['data_option']['default']).to be(false)
      expect(json_response['data_type']).to eq('boolean')

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      params['data_type'] = 'input'
      params['data_option'] = {
        default:   'test',
        type:      'text',
        maxlength: 120
      }

      put "/api/v1/object_manager_attributes/#{json_response['id']}", params: params, as: :json
      expect(response).to have_http_status(:unprocessable_entity)
      expect(json_response).to be_truthy
      expect(json_response['error']).to be_truthy

    end

    it 'does verify if attribute type can be changed (08)', db_strategy: :reset do

      params = {
        name:        "customerdescription_#{SecureRandom.uuid.tr('-', '_')}",
        object:      'Ticket',
        display:     "custom description #{SecureRandom.uuid.tr('-', '_')}",
        active:      true,
        data_type:   'input',
        data_option: {
          default:   'test',
          type:      'text',
          maxlength: 120,
        },
        screens:     {
          create_middle: {
            'ticket.customer': {
              shown:      true,
              item_class: 'column'
            },
            'ticket.agent':    {
              shown:      true,
              item_class: 'column'
            }
          },
          edit:          {
            'ticket.customer': {
              shown: true
            },
            'ticket.agent':    {
              shown: true
            }
          },
        },
      }

      authenticated_as(admin)
      post '/api/v1/object_manager_attributes', params: params, as: :json

      expect(response).to have_http_status(:created) # created

      expect(json_response).to be_truthy
      expect(json_response['data_option']['default']).to eq('test')
      expect(json_response['data_type']).to eq('input')

      migration = ObjectManager::Attribute.migration_execute
      expect(migration).to be(true)

      params['data_type'] = 'select'
      params['data_option'] = {
        default: 'fuu',
        options: {
          key1: 'foo',
          key2: 'fuu',
        }
      }

      put "/api/v1/object_manager_attributes/#{json_response['id']}", params: params, as: :json

      expect(response).to have_http_status(:ok)
      expect(json_response).to be_truthy
      expect(json_response['data_option']['default']).to eq('test')
      expect(json_response['data_option_new']['default']).to eq('fuu')
      expect(json_response['data_type']).to eq('select')
    end

    it "doesn't let to update item that doesn't exist", authenticated_as: -> { admin } do
      params = {
        active:      true,
        data_option: {
          type:      'text',
          maxlength: 200
        },
        data_type:   'input',
        display:     'Test 7',
        name:        'attribute_that_doesnt_exist',
        object:      'User',
      }

      # update the object
      put '/api/v1/object_manager_attributes/abc', params: params, as: :json
      expect(response).to have_http_status(:unprocessable_entity)
    end

    context 'position handling', authenticated_as: -> { admin } do
      let(:base_params) do
        {
          name:        "customerdescription_#{SecureRandom.uuid.tr('-', '_')}",
          object:      'Ticket',
          display:     "custom description #{SecureRandom.uuid.tr('-', '_')}",
          active:      true,
          data_type:   'input',
          data_option: {
            default:   'test',
            type:      'text',
            maxlength: 120,
          },
        }
      end

      let(:new_attribute_id)     { json_response['id'] }
      let(:new_attribute_object) { ObjectManager::Attribute.find new_attribute_id }

      before { post '/api/v1/object_manager_attributes', params: params, as: :json }

      context 'when creating a new attribute' do
        let(:params) { base_params }

        context 'with no position attribute provided' do
          let(:maximum_position) do
            ObjectManager::Attribute
              .for_object(params[:object])
              .maximum(:position)
          end

          it 'defaults to the maximum available position' do
            expect(new_attribute_object.position).to eq maximum_position
          end
        end

        context 'with a position attribute given' do
          let(:position) { 50 }
          let(:params)   { base_params.merge(position: position) }

          it 'defaults to given position' do
            expect(new_attribute_object.position).to eq position
          end
        end
      end

      context 'when updating an existing attribute' do
        let(:alternative_position) { 123 }
        let(:alternative_display)  { 'another description' }
        let(:params)               { base_params }
        let(:alternative_params)   { base_params.merge(display: alternative_display) }

        before do
          new_attribute_object.update! position: alternative_position

          put "/api/v1/object_manager_attributes/#{new_attribute_id}", params: alternative_params, as: :json

          new_attribute_object.reload
        end

        # confirm that test build up was correct
        it 'request succeeds' do
          expect(new_attribute_object.display).to eq alternative_display
        end

        # https://github.com/zammad/zammad/issues/3044
        it 'position did not reset' do
          expect(new_attribute_object.position).to eq alternative_position
        end
      end
    end
  end
end