# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ require 'rails_helper' require 'models/application_model_examples' require 'models/concerns/can_be_imported_examples' require 'models/concerns/has_collection_update_examples' require 'models/concerns/has_xss_sanitized_note_examples' RSpec.describe Ticket::State, type: :model do it_behaves_like 'ApplicationModel' it_behaves_like 'CanBeImported' it_behaves_like 'HasCollectionUpdate', collection_factory: :ticket_state it_behaves_like 'HasXssSanitizedNote', model_factory: :ticket_state describe 'Default state' do describe 'of whole table:' do it 'has default records' do expect(described_class.pluck(:name)) .to contain_exactly('closed', 'merged', 'new', 'open', 'pending close', 'pending reminder') end end describe 'of "new" state:' do it 'is the sole #default_create state' do expect(described_class.where(default_create: true)) .to contain_exactly(described_class.find_by(name: 'new')) end end describe 'of "open" state:' do it 'is the sole #default_follow_up state' do expect(described_class.where(default_follow_up: true)) .to contain_exactly(described_class.find_by(name: 'open')) end end end describe 'Class methods:' do describe '.by_category' do it 'looks up states by category' do expect(described_class.by_category(:open)) .to be_an(ActiveRecord::Relation) .and include(instance_of(described_class)) end context 'with invalid category name' do it 'raises ArgumentError' do expect { described_class.by_category(:invalidcategoryname) } .to raise_error(ArgumentError) end end end end describe 'Attributes:' do describe '#default_create' do let!(:original_default) { described_class.find_by(default_create: true) } context 'for newly created record' do subject!(:state) { build(:ticket_state, default_create: default_create) } context 'when true' do let(:default_create) { true } it 'unsets previous default' do expect { state.save } .to change { original_default.reload.default_create }.to(false) .and not_change { described_class.where(default_create: true).count } end end context 'when false' do let(:default_create) { false } it 'does not alter existing default' do expect { state.save } .to not_change { described_class.find_by(default_create: true) } .and not_change { described_class.where(default_create: true).count } end end end context 'for existing record' do subject!(:state) { create(:ticket_state, default_create: default_create) } context 'when true' do let(:default_create) { true } context 'and updated to false' do it 'assigns Ticket::State.first as default' do expect { state.update(default_create: false) } .to change { described_class.first.default_create }.to(true) .and not_change { described_class.where(default_create: true).count } end end context 'and destroyed' do it 'assigns Ticket::State.first as default' do expect { state.destroy } .to change { described_class.first.default_create }.to(true) .and not_change { described_class.where(default_create: true).count } end end end context 'when false' do let(:default_create) { false } context 'and updated to true' do it 'unsets previous default' do expect { state.update(default_create: true) } .to change { original_default.reload.default_create }.to(false) .and not_change { described_class.where(default_create: true).count } end end context 'and destroyed' do it 'does not alter existing default' do expect { state.destroy } .to not_change { described_class.find_by(default_create: true) } .and not_change { described_class.where(default_create: true).count } end end end end end describe '#default_follow_up' do let!(:original_default) { described_class.find_by(default_follow_up: true) } context 'for newly created record' do subject!(:state) { build(:ticket_state, default_follow_up: default_follow_up) } context 'when true' do let(:default_follow_up) { true } it 'unsets previous default' do expect { state.save } .to change { original_default.reload.default_follow_up }.to(false) .and not_change { described_class.where(default_follow_up: true).count } end end context 'when false' do let(:default_follow_up) { false } it 'does not alter existing default' do expect { state.save } .to not_change { described_class.find_by(default_follow_up: true) } .and not_change { described_class.where(default_follow_up: true).count } end end end context 'for existing record' do subject!(:state) { create(:ticket_state, default_follow_up: default_follow_up) } context 'when true' do let(:default_follow_up) { true } context 'and updated to false' do it 'assigns Ticket::State.first as default' do expect { state.update(default_follow_up: false) } .to change { described_class.first.default_follow_up }.to(true) .and not_change { described_class.where(default_follow_up: true).count } end end context 'and destroyed' do it 'assigns Ticket::State.first as default' do expect { state.destroy } .to change { described_class.first.default_follow_up }.to(true) .and not_change { described_class.where(default_follow_up: true).count } end end end context 'when false' do let(:default_follow_up) { false } context 'and updated to true' do it 'unsets previous default' do expect { state.update(default_follow_up: true) } .to change { original_default.reload.default_follow_up }.to(false) .and not_change { described_class.where(default_follow_up: true).count } end end context 'and destroyed' do it 'does not alter existing default' do expect { state.destroy } .to not_change { described_class.find_by(default_follow_up: true) } .and not_change { described_class.where(default_follow_up: true).count } end end end end end end describe 'Callbacks' do let(:attr) { ObjectManager::Attribute.get(object: 'Ticket', name: 'state_id') } before { Setting.set('system_init_done', true) } it 'updates state_id attribute when a new state is added' do new_state = create(:ticket_state, state_type: Ticket::StateType.lookup(name: 'open')) expect(attr.screens) .to include( 'create_middle' => include( 'ticket.agent' => include('filter' => include(new_state.id)), 'ticket.customer' => include('filter' => not_include(new_state.id)) ), 'edit' => include( 'ticket.agent' => include('filter' => include(new_state.id)), 'ticket.customer' => include('filter' => include(new_state.id)) ) ) end context 'with an existing state' do let(:state) { create(:ticket_state, state_type: Ticket::StateType.lookup(name: 'new')) } it 'updates state_id attribute when a state is modified' do state.update! state_type: Ticket::StateType.lookup(name: 'open') expect(attr.screens) .to include( 'create_middle' => include( 'ticket.agent' => include('filter' => include(state.id)), 'ticket.customer' => include('filter' => not_include(state.id)) ), 'edit' => include( 'ticket.agent' => include('filter' => include(state.id)), 'ticket.customer' => include('filter' => include(state.id)) ) ) end it 'updated state_id attribute does not include inactive states (#5268)' do state.update! active: false expect(attr.screens) .to include( 'create_middle' => include( 'ticket.agent' => include('filter' => not_include(state.id)), 'ticket.customer' => include('filter' => not_include(state.id)) ), 'edit' => include( 'ticket.agent' => include('filter' => not_include(state.id)), 'ticket.customer' => include('filter' => not_include(state.id)) ) ) end it 'updates state_id attribute when a state is destroyed' do state.destroy! expect(attr.screens) .to include( 'create_middle' => include( 'ticket.agent' => include('filter' => not_include(state.id)), 'ticket.customer' => include('filter' => not_include(state.id)) ), 'edit' => include( 'ticket.agent' => include('filter' => not_include(state.id)), 'ticket.customer' => include('filter' => not_include(state.id)) ) ) end end end end