# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ require 'rails_helper' RSpec.describe Service::Ticket::Article::Create, current_user_id: -> { user.id } do subject(:service) { described_class.new(current_user: user) } let(:ticket) { create(:ticket, customer: create(:agent)) } let(:user) { create(:agent, groups: [ticket.group]) } let(:payload) { { body: 'test' } } let(:article) { service.execute(article_data: payload, ticket: ticket) } describe '#execute' do it 'creates an article' do expect(article).to be_persisted end it 'creates an article even if contains wrong ticket id' do payload[:ticket_id] = 123_456 expect(article).to be_persisted.and(have_attributes(ticket_id: ticket.id)) end describe 'time accounting', :aggregate_failures do let(:time_accounting_enabled) { true } before do Setting.set('time_accounting', time_accounting_enabled) payload[:time_unit] = 60 end it 'adds time accounting without type' do expect(article.ticket_time_accounting.time_unit).to be_present expect(article.ticket_time_accounting.type).to be_nil end context 'with accounting type' do let(:accounted_time_type) { create(:ticket_time_accounting_type) } before do payload[:accounted_time_type] = accounted_time_type end it 'adds time accounting with type' do expect(article.ticket_time_accounting.time_unit).to be_present expect(article.ticket_time_accounting.type).to eq(accounted_time_type) end end context 'when time accounting is not enabled' do let(:time_accounting_enabled) { false } it 'does not save article and raises error' do expect { article } .to raise_error(%r{Time Accounting is not enabled}) end end end describe 'to and cc fields processing' do it 'translates to and cc fields from arrays to strings' do payload.merge!({ to: %w[a b], cc: %w[b c] }) expect(article).to have_attributes(to: 'a, b', cc: 'b, c') end it 'handles string and nil values' do payload.merge!({ to: 'a,b', cc: nil }) expect(article).to have_attributes(to: 'a,b', cc: '') end end describe 'sender processing' do context 'when user is agent' do it 'agent is set to agent' do expect(article.sender.name).to eq 'Agent' end it 'preserves original value if given' do payload[:sender] = 'Customer' expect(article.sender.name).to eq 'Customer' end end context 'when user is customer' do let(:user) { ticket.customer } let(:ticket) { create(:ticket, customer: create(:customer)) } it 'ensures sender is set to customer' do expect(article.sender.name).to eq 'Customer' end end # Agent-Customer is incorrectly detected as Agent in a group he has no access to # https://github.com/zammad/zammad/issues/4649 context 'when user is agent-customer' do let(:user) { ticket.customer } it 'ensures sender is set to customer' do expect(article.sender.name).to eq 'Agent' end end end describe 'processing for customer' do context 'when user is customer' do let(:user) { ticket.customer } let(:ticket) { create(:ticket, customer: create(:customer)) } it 'ensures internal is false' do payload[:internal] = true expect(article.internal).to be_falsey end it 'changes type from web to note' do payload[:type] = 'phone' expect(article.type.name).to eq('note') end end # Agent-Customer is incorrectly detected as Agent in a group he has no access to # https://github.com/zammad/zammad/issues/4649 context 'when user is agent-customer' do let(:user) { ticket.customer } it 'ensures internal is false' do payload[:internal] = false expect(article.internal).to be_falsey end it 'changes type from web to note' do payload[:type] = 'phone' expect(article.type.name).to eq('phone') end end context 'when user is agent' do it 'allows internal to be true' do payload[:internal] = true expect(article.internal).to be_truthy end it 'applies no changes to type' do payload[:type] = 'phone' expect(article.type.name).to eq('phone') end end end describe 'transforming attachments' do it 'adds attachments with inlines' do payload[:content_type] = 'text/html' payload[:body] = 'some body Red dot' expect(article.attachments).to be_one end context 'when attachment is uploaded' do let(:form_id) { SecureRandom.uuid } let(:taskbar) { create(:taskbar, user_id: user.id, state: { form_id: }) } before do taskbar file_name = 'file1.png' file_type = 'image/png' file_content = Base64.strict_encode64('file1') UploadCache.new(form_id).tap do |cache| cache.add( data: file_content, filename: file_name, preferences: { 'Content-Type' => file_type }, created_by_id: 1, ) end end it 'adds attachments with inlines and updates taskbar state', aggregate_failures: true do payload[:content_type] = 'text/html' payload[:attachments] = { files: [], form_id:, } payload[:body] = "some body alt='Red dot' />" expect(article.attachments).to be_one expect(taskbar.reload.state).to eq({}) end end end describe 'mentions', aggregate_failures: true do def text_blob_with(user) "Lorem ipsum dolor #{user.fullname}" end let(:payload) { { body: body } } context 'when author can mention other users' do context 'when valid user is mentioned' do let(:body) { text_blob_with(user) } it 'create ticket with mentions' do expect { article }.to change(Mention, :count).by(1) end end context 'when user without access to the ticket is mentioned' do let(:body) { text_blob_with(create(:agent)) } it 'raises an error with one of mentions being invalid' do expect { article } .to raise_error(ActiveRecord::RecordInvalid) expect(Mention.count).to eq(0) end end end context 'when author does not have permissions to create mentions' do let(:user) { create(:customer) } let(:body) { text_blob_with(create(:agent, groups: [ticket.group])) } it 'raise an error if author does not have permissions to create mentions' do expect { article } .to raise_error(Pundit::NotAuthorizedError) expect(Mention.count).to eq(0) end end end end end