2022-01-01 13:38:12 +00:00
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
2021-06-01 12:20:20 +00:00
2019-02-26 11:00:46 +00:00
require 'rails_helper'
require 'models/application_model_examples'
2021-04-12 09:49:26 +00:00
require 'models/concerns/has_xss_sanitized_note_examples'
2019-02-26 11:00:46 +00:00
RSpec . describe Trigger , type : :model do
2019-04-15 01:41:17 +00:00
subject ( :trigger ) { create ( :trigger , condition : condition , perform : perform ) }
2019-02-26 11:00:46 +00:00
2019-04-15 01:41:17 +00:00
it_behaves_like 'ApplicationModel' , can_assets : { selectors : % i [ condition perform ] }
2021-04-12 09:49:26 +00:00
it_behaves_like 'HasXssSanitizedNote' , model_factory : :trigger
2019-02-26 11:00:46 +00:00
2020-03-17 07:52:57 +00:00
describe 'validation' do
let ( :condition ) do
{ 'ticket.action' = > { 'operator' = > 'is' , 'value' = > 'create' } }
end
let ( :perform ) do
{ 'ticket.title' = > { 'value' = > 'triggered' } }
end
context 'notification.email' do
context 'missing recipient' do
let ( :perform ) do
{
'notification.email' = > {
'subject' = > 'Hello' ,
'body' = > 'World!'
}
}
end
it 'raises an error' do
expect { trigger . save! } . to raise_error ( Exceptions :: UnprocessableEntity , 'Invalid perform notification.email, recipient is missing!' )
end
end
end
end
2019-04-15 01:41:17 +00:00
describe 'Send-email triggers' do
before do
2020-06-22 09:57:45 +00:00
described_class . destroy_all # Default DB state includes three sample triggers
trigger # create subject trigger
2019-02-26 11:00:46 +00:00
end
2019-03-11 07:28:18 +00:00
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > 'ticket_customer' ,
'subject' = > 'foo' ,
2019-03-11 17:17:08 +00:00
'body' = > 'some body with >snip<#{article.body_as_html}>/snip<' , # rubocop:disable Lint/InterpolationCheck
2019-03-11 07:28:18 +00:00
}
}
end
2021-08-16 11:34:05 +00:00
shared_examples 'include ticket attachment' do
context 'notification.email include_attachments' do
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > 'ticket_customer' ,
'subject' = > 'Example subject' ,
'body' = > 'Example body' ,
}
} . deep_merge ( additional_options ) . deep_stringify_keys
end
let ( :ticket ) { create ( :ticket ) }
shared_examples 'add a new article' do
it 'adds a new article' do
expect { TransactionDispatcher . commit }
. to change ( ticket . articles , :count ) . by ( 1 )
end
end
shared_examples 'add attachment to new article' do
include_examples 'add a new article'
it 'adds attachment to the new article' do
ticket && trigger
TransactionDispatcher . commit
article = ticket . articles . last
expect ( article . type . name ) . to eq ( 'email' )
expect ( article . sender . name ) . to eq ( 'System' )
expect ( article . attachments . count ) . to eq ( 1 )
expect ( article . attachments [ 0 ] . filename ) . to eq ( 'some_file.pdf' )
expect ( article . attachments [ 0 ] . preferences [ 'Content-ID' ] ) . to eq ( 'image/pdf@01CAB192.K8H512Y9' )
end
end
shared_examples 'does not add attachment to new article' do
include_examples 'add a new article'
it 'does not add attachment to the new article' do
ticket && trigger
TransactionDispatcher . commit
article = ticket . articles . last
expect ( article . type . name ) . to eq ( 'email' )
expect ( article . sender . name ) . to eq ( 'System' )
expect ( article . attachments . count ) . to eq ( 0 )
end
end
context 'with include attachment present' do
let ( :additional_options ) do
{
'notification.email' = > {
include_attachments : 'true'
}
}
end
context 'when ticket has an attachment' do
before do
UserInfo . current_user_id = 1
ticket_article = create ( :ticket_article , ticket : ticket )
Store . add (
object : 'Ticket::Article' ,
o_id : ticket_article . id ,
data : 'dGVzdCAxMjM=' ,
filename : 'some_file.pdf' ,
preferences : {
'Content-Type' : 'image/pdf' ,
'Content-ID' : 'image/pdf@01CAB192.K8H512Y9' ,
} ,
created_by_id : 1 ,
)
end
include_examples 'add attachment to new article'
end
context 'when ticket does not have an attachment' do
include_examples 'does not add attachment to new article'
end
end
context 'with include attachment not present' do
let ( :additional_options ) do
{
'notification.email' = > {
include_attachments : 'false'
}
}
end
context 'when ticket has an attachment' do
before do
UserInfo . current_user_id = 1
ticket_article = create ( :ticket_article , ticket : ticket )
Store . add (
object : 'Ticket::Article' ,
o_id : ticket_article . id ,
data : 'dGVzdCAxMjM=' ,
filename : 'some_file.pdf' ,
preferences : {
'Content-Type' : 'image/pdf' ,
'Content-ID' : 'image/pdf@01CAB192.K8H512Y9' ,
} ,
created_by_id : 1 ,
)
end
include_examples 'does not add attachment to new article'
end
context 'when ticket does not have an attachment' do
include_examples 'does not add attachment to new article'
end
end
end
end
2019-03-11 07:28:18 +00:00
context 'for condition "ticket created"' do
let ( :condition ) do
{ 'ticket.action' = > { 'operator' = > 'is' , 'value' = > 'create' } }
end
context 'when ticket is created directly' do
let! ( :ticket ) { create ( :ticket ) }
it 'fires (without altering ticket state)' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2019-04-15 01:41:17 +00:00
. to change ( Ticket :: Article , :count ) . by ( 1 )
2019-03-11 07:28:18 +00:00
. and not_change { ticket . reload . state . name } . from ( 'new' )
end
end
2021-05-07 14:25:31 +00:00
context 'when ticket has tags' do
let ( :tag1 ) { create ( :'tag/item' , name : 't1' ) }
let ( :tag2 ) { create ( :'tag/item' , name : 't2' ) }
let ( :tag3 ) { create ( :'tag/item' , name : 't3' ) }
let! ( :ticket ) do
ticket = create ( :ticket )
create ( :tag , o : ticket , tag_item : tag1 )
create ( :tag , o : ticket , tag_item : tag2 )
create ( :tag , o : ticket , tag_item : tag3 )
ticket
end
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > 'ticket_customer' ,
'subject' = > 'foo' ,
'body' = > 'some body with #{ticket.tags}' , # rubocop:disable Lint/InterpolationCheck
}
}
end
it 'fires body with replaced tags' do
2021-05-20 06:59:02 +00:00
TransactionDispatcher . commit
2021-05-07 14:25:31 +00:00
expect ( Ticket :: Article . last . body ) . to eq ( 'some body with t1, t2, t3' )
end
end
2019-03-11 07:28:18 +00:00
context 'when ticket is created via Channel::EmailParser.process' do
before { create ( :email_address , groups : [ Group . first ] ) }
2019-04-15 01:41:17 +00:00
2020-02-18 19:51:31 +00:00
let ( :raw_email ) { File . read ( Rails . root . join ( 'test/data/mail/mail001.box' ) ) }
2019-03-11 07:28:18 +00:00
it 'fires (without altering ticket state)' do
expect { Channel :: EmailParser . new . process ( { } , raw_email ) }
2019-04-15 01:41:17 +00:00
. to change ( Ticket , :count ) . by ( 1 )
2019-03-11 07:28:18 +00:00
. and change { Ticket :: Article . count } . by ( 2 )
expect ( Ticket . last . state . name ) . to eq ( 'new' )
end
end
2019-03-11 17:17:08 +00:00
context 'when ticket is created via Channel::EmailParser.process with inline image' do
before { create ( :email_address , groups : [ Group . first ] ) }
2019-04-15 01:41:17 +00:00
2020-02-18 19:51:31 +00:00
let ( :raw_email ) { File . read ( Rails . root . join ( 'test/data/mail/mail010.box' ) ) }
2019-03-11 17:17:08 +00:00
it 'fires (without altering ticket state)' do
expect { Channel :: EmailParser . new . process ( { } , raw_email ) }
2019-04-15 01:41:17 +00:00
. to change ( Ticket , :count ) . by ( 1 )
2019-03-11 17:17:08 +00:00
. and change { Ticket :: Article . count } . by ( 2 )
expect ( Ticket . last . state . name ) . to eq ( 'new' )
article = Ticket :: Article . last
expect ( article . type . name ) . to eq ( 'email' )
expect ( article . sender . name ) . to eq ( 'System' )
expect ( article . attachments . count ) . to eq ( 1 )
expect ( article . attachments [ 0 ] . filename ) . to eq ( 'image001.jpg' )
expect ( article . attachments [ 0 ] . preferences [ 'Content-ID' ] ) . to eq ( 'image001.jpg@01CDB132.D8A510F0' )
expect ( article . body ) . to eq ( << ~ RAW . chomp
some body with & gt ; snip & lt ; < div >
< p > Herzliche Grüße aus Oberalteich sendet Herrn Smith < / p>
< p > < / p>
< p > Sepp Smith - Dipl . Ing . agr . ( FH ) < / p>
< p > Geschäftsführer der example Straubing - Bogen < / p>
< p > Klosterhof 1 | 94327 Bogen - Oberalteich < / p>
< p > Tel : 09422 - 505601 | Fax : 09422 - 505620 < / p>
2021-01-08 15:02:19 +00:00
< p > < span > Internet : < a href = " http://example-straubing-bogen.de/ " rel = " nofollow noreferrer noopener " target = " _blank " > < span style = " color:blue; " > http : / /ex ample - straubing - bogen . de < / span>< / a > < / span>< / p >
< p > < span lang = " EN-US " > Facebook : < / span><a href="http: / / facebook . de / examplesrbog " rel= " nofollow noreferrer noopener " target= " _blank " ><span lang= " EN - US " style= " color : blue ; " >http://facebook.de/examplesrbog</span></a><span lang= " EN - US " ></span></p>
< p > < b > < span style = " color:navy; " > < img border = " 0 " src = " cid:image001.jpg@01CDB132.D8A510F0 " alt = " Beschreibung: Beschreibung: efqmLogo " style = " width:60px;height:19px; " > < / span>< / b > < b > < span lang = " EN-US " style = " color:navy; " > - European Foundation für Quality Management < / span>< / b > < span lang = " EN-US " > < / span>< / p >
< p > < span lang = " EN-US " > < p > < / p>< /s pan > < / p>
2019-03-11 17:17:08 +00:00
< / div>> /sni p & lt ;
RAW
)
end
end
2020-03-17 07:52:57 +00:00
context 'notification.email recipient' do
let! ( :ticket ) { create ( :ticket ) }
let! ( :recipient1 ) { create ( :user , email : 'test1@zammad-test.com' ) }
let! ( :recipient2 ) { create ( :user , email : 'test2@zammad-test.com' ) }
let! ( :recipient3 ) { create ( :user , email : 'test3@zammad-test.com' ) }
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > recipient ,
'subject' = > 'Hello' ,
'body' = > 'World!'
}
}
end
2021-05-20 06:59:02 +00:00
before { TransactionDispatcher . commit }
2020-03-17 07:52:57 +00:00
context 'mix of recipient group keyword and single recipient users' do
let ( :recipient ) { [ 'ticket_customer' , " userid_ #{ recipient1 . id } " , " userid_ #{ recipient2 . id } " , " userid_ #{ recipient3 . id } " ] }
it 'contains all recipients' do
expect ( ticket . articles . last . to ) . to eq ( " #{ ticket . customer . email } , #{ recipient1 . email } , #{ recipient2 . email } , #{ recipient3 . email } " )
end
context 'duplicate recipient' do
let ( :recipient ) { [ 'ticket_customer' , " userid_ #{ ticket . customer . id } " ] }
it 'contains only one recipient' do
expect ( ticket . articles . last . to ) . to eq ( ticket . customer . email . to_s )
end
end
end
context 'list of single users only' do
let ( :recipient ) { [ " userid_ #{ recipient1 . id } " , " userid_ #{ recipient2 . id } " , " userid_ #{ recipient3 . id } " ] }
it 'contains all recipients' do
expect ( ticket . articles . last . to ) . to eq ( " #{ recipient1 . email } , #{ recipient2 . email } , #{ recipient3 . email } " )
end
2020-03-18 17:25:24 +00:00
context 'assets' do
it 'resolves Users from recipient list' do
expect ( trigger . assets ( { } ) [ :User ] . keys ) . to include ( recipient1 . id , recipient2 . id , recipient3 . id )
end
context 'single entry' do
let ( :recipient ) { " userid_ #{ recipient1 . id } " }
it 'resolves User from recipient list' do
expect ( trigger . assets ( { } ) [ :User ] . keys ) . to include ( recipient1 . id )
end
end
end
2020-03-17 07:52:57 +00:00
end
context 'recipient group keyword only' do
let ( :recipient ) { 'ticket_customer' }
it 'contains matching recipient' do
expect ( ticket . articles . last . to ) . to eq ( ticket . customer . email . to_s )
end
end
end
2020-06-02 11:01:16 +00:00
context 'active S/MIME integration' do
before do
Setting . set ( 'smime_integration' , true )
create ( :smime_certificate , :with_private , fixture : system_email_address )
create ( :smime_certificate , fixture : customer_email_address )
end
let ( :system_email_address ) { 'smime1@example.com' }
let ( :customer_email_address ) { 'smime2@example.com' }
let ( :email_address ) { create ( :email_address , email : system_email_address ) }
let ( :group ) { create ( :group , email_address : email_address ) }
2020-06-19 09:17:18 +00:00
let ( :customer ) { create ( :customer , email : customer_email_address ) }
2020-06-02 11:01:16 +00:00
let ( :security_preferences ) { Ticket :: Article . last . preferences [ :security ] }
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > 'ticket_customer' ,
'subject' = > 'Subject dummy.' ,
'body' = > 'Body dummy.' ,
} . merge ( security_configuration )
}
end
let! ( :ticket ) { create ( :ticket , group : group , customer : customer ) }
context 'sending articles' do
before do
2021-05-20 06:59:02 +00:00
TransactionDispatcher . commit
2020-06-02 11:01:16 +00:00
end
context 'expired certificate' do
let ( :system_email_address ) { 'expiredsmime1@example.com' }
let ( :security_configuration ) do
{
'sign' = > 'always' ,
'encryption' = > 'always' ,
}
end
it 'creates unsigned article' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be false
expect ( security_preferences [ :encryption ] [ :success ] ) . to be true
end
end
context 'sign and encryption not set' do
let ( :security_configuration ) { { } }
it 'does not sign or encrypt' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be false
expect ( security_preferences [ :encryption ] [ :success ] ) . to be false
end
end
context 'sign and encryption disabled' do
let ( :security_configuration ) do
{
'sign' = > 'no' ,
'encryption' = > 'no' ,
}
end
it 'does not sign or encrypt' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be false
expect ( security_preferences [ :encryption ] [ :success ] ) . to be false
end
end
context 'sign is enabled' do
let ( :security_configuration ) do
{
'sign' = > 'always' ,
'encryption' = > 'no' ,
}
end
it 'signs' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be true
expect ( security_preferences [ :encryption ] [ :success ] ) . to be false
end
end
context 'encryption enabled' do
let ( :security_configuration ) do
{
'sign' = > 'no' ,
'encryption' = > 'always' ,
}
end
it 'encrypts' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be false
expect ( security_preferences [ :encryption ] [ :success ] ) . to be true
end
end
context 'sign and encryption enabled' do
let ( :security_configuration ) do
{
'sign' = > 'always' ,
'encryption' = > 'always' ,
}
end
it 'signs and encrypts' do
expect ( security_preferences [ :sign ] [ :success ] ) . to be true
expect ( security_preferences [ :encryption ] [ :success ] ) . to be true
end
end
end
context 'discard' do
context 'sign' do
let ( :security_configuration ) do
{
'sign' = > 'discard' ,
}
end
context 'group without certificate' do
let ( :group ) { create ( :group ) }
it 'does not fire' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-06-02 11:01:16 +00:00
. to change ( Ticket :: Article , :count ) . by ( 0 )
end
end
end
context 'encryption' do
let ( :security_configuration ) do
{
'encryption' = > 'discard' ,
}
end
context 'customer without certificate' do
let ( :customer ) { create ( :customer ) }
it 'does not fire' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-06-02 11:01:16 +00:00
. to change ( Ticket :: Article , :count ) . by ( 0 )
end
end
end
context 'mixed' do
context 'sign' do
let ( :security_configuration ) do
{
'encryption' = > 'always' ,
'sign' = > 'discard' ,
}
end
context 'group without certificate' do
let ( :group ) { create ( :group ) }
it 'does not fire' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-06-02 11:01:16 +00:00
. to change ( Ticket :: Article , :count ) . by ( 0 )
end
end
end
context 'encryption' do
let ( :security_configuration ) do
{
'encryption' = > 'discard' ,
'sign' = > 'always' ,
}
end
context 'customer without certificate' do
let ( :customer ) { create ( :customer ) }
it 'does not fire' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-06-02 11:01:16 +00:00
. to change ( Ticket :: Article , :count ) . by ( 0 )
end
end
end
end
end
end
2021-08-16 11:34:05 +00:00
include_examples 'include ticket attachment'
2019-03-11 07:28:18 +00:00
end
context 'for condition "ticket updated"' do
let ( :condition ) do
{ 'ticket.action' = > { 'operator' = > 'is' , 'value' = > 'update' } }
end
2021-05-20 06:59:02 +00:00
let! ( :ticket ) { create ( :ticket ) . tap { TransactionDispatcher . commit } }
2019-03-11 07:28:18 +00:00
context 'when new article is created directly' do
context 'with empty #preferences hash' do
2020-06-22 09:57:45 +00:00
let! ( :article ) { create ( :ticket_article , ticket : ticket ) }
2019-03-11 07:28:18 +00:00
it 'fires (without altering ticket state)' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2019-03-11 07:28:18 +00:00
. to change { ticket . reload . articles . count } . by ( 1 )
. and not_change { ticket . reload . state . name } . from ( 'new' )
end
end
context 'with #preferences { "send-auto-response" => false }' do
let! ( :article ) do
create ( :ticket_article ,
ticket : ticket ,
preferences : { 'send-auto-response' = > false } )
end
it 'does not fire' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2019-03-11 07:28:18 +00:00
. not_to change { ticket . reload . articles . count }
end
end
end
context 'when new article is created via Channel::EmailParser.process' do
context 'with a regular message' do
let! ( :article ) do
create ( :ticket_article ,
ticket : ticket ,
2021-05-12 11:37:44 +00:00
message_id : raw_email [ %r{ (?<=^References: ) \ S* } ] ,
subject : raw_email [ %r{ (?<=^Subject: Re: ).*$ } ] )
2019-03-11 07:28:18 +00:00
end
2020-02-18 19:51:31 +00:00
let ( :raw_email ) { File . read ( Rails . root . join ( 'test/data/mail/mail005.box' ) ) }
2019-03-11 07:28:18 +00:00
it 'fires (without altering ticket state)' do
expect { Channel :: EmailParser . new . process ( { } , raw_email ) }
. to not_change { Ticket . count }
. and change { ticket . reload . articles . count } . by ( 2 )
. and not_change { ticket . reload . state . name } . from ( 'new' )
end
end
context 'with delivery-failed "bounce message"' do
let! ( :article ) do
create ( :ticket_article ,
ticket : ticket ,
2021-05-12 11:37:44 +00:00
message_id : raw_email [ %r{ (?<=^Message-ID: ) \ S* } ] )
2019-03-11 07:28:18 +00:00
end
2020-02-18 19:51:31 +00:00
let ( :raw_email ) { File . read ( Rails . root . join ( 'test/data/mail/mail055.box' ) ) }
2019-03-11 07:28:18 +00:00
it 'does not fire' do
expect { Channel :: EmailParser . new . process ( { } , raw_email ) }
. to change { ticket . reload . articles . count } . by ( 1 )
end
end
end
end
2020-02-20 12:34:16 +00:00
context 'with condition execution_time.calendar_id' do
let ( :calendar ) { create ( :calendar ) }
let ( :perform ) do
{ 'ticket.title' = > { 'value' = > 'triggered' } }
end
let! ( :ticket ) { create ( :ticket , title : 'Test Ticket' ) }
context 'is in working time' do
let ( :condition ) do
2020-03-03 09:39:16 +00:00
{ 'ticket.state_id' = > { 'operator' = > 'is' , 'value' = > Ticket :: State . all . pluck ( :id ) } , 'execution_time.calendar_id' = > { 'operator' = > 'is in working time' , 'value' = > calendar . id } }
2020-02-20 12:34:16 +00:00
end
it 'does trigger only in working time' do
travel_to Time . zone . parse ( '2020-02-12T12:00:00Z0' )
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit } . to change { ticket . reload . title } . to ( 'triggered' )
2020-02-20 12:34:16 +00:00
end
it 'does not trigger out of working time' do
travel_to Time . zone . parse ( '2020-02-12T02:00:00Z0' )
2021-05-20 06:59:02 +00:00
TransactionDispatcher . commit
2020-02-20 12:34:16 +00:00
expect ( ticket . reload . title ) . to eq ( 'Test Ticket' )
end
end
context 'is not in working time' do
let ( :condition ) do
{ 'execution_time.calendar_id' = > { 'operator' = > 'is not in working time' , 'value' = > calendar . id } }
end
it 'does not trigger in working time' do
travel_to Time . zone . parse ( '2020-02-12T12:00:00Z0' )
2021-05-20 06:59:02 +00:00
TransactionDispatcher . commit
2020-02-20 12:34:16 +00:00
expect ( ticket . reload . title ) . to eq ( 'Test Ticket' )
end
it 'does trigger out of working time' do
travel_to Time . zone . parse ( '2020-02-12T02:00:00Z0' )
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit } . to change { ticket . reload . title } . to ( 'triggered' )
2020-02-20 12:34:16 +00:00
end
end
end
2020-03-24 16:15:34 +00:00
context 'with article last sender equals system address' do
let! ( :ticket ) { create ( :ticket ) }
let ( :perform ) do
{
'notification.email' = > {
'recipient' = > 'article_last_sender' ,
'subject' = > 'foo last sender' ,
'body' = > 'some body with >snip<#{article.body_as_html}>/snip<' , # rubocop:disable Lint/InterpolationCheck
}
}
end
let ( :condition ) do
{ 'ticket.state_id' = > { 'operator' = > 'is' , 'value' = > Ticket :: State . all . pluck ( :id ) } }
end
let! ( :system_address ) do
create ( :email_address )
end
context 'article with from equal to the a system address' do
let! ( :article ) do
create ( :ticket_article ,
ticket : ticket ,
from : system_address . email , )
end
it 'does not trigger because of the last article is created my system address' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit } . to change { ticket . reload . articles . count } . by ( 0 )
2020-03-24 16:15:34 +00:00
expect ( Ticket :: Article . where ( ticket : ticket ) . last . subject ) . not_to eq ( 'foo last sender' )
expect ( Ticket :: Article . where ( ticket : ticket ) . last . to ) . not_to eq ( system_address . email )
end
end
context 'article with reply_to equal to the a system address' do
let! ( :article ) do
create ( :ticket_article ,
ticket : ticket ,
from : system_address . email ,
reply_to : system_address . email , )
end
it 'does not trigger because of the last article is created my system address' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit } . to change { ticket . reload . articles . count } . by ( 0 )
2020-03-24 16:15:34 +00:00
expect ( Ticket :: Article . where ( ticket : ticket ) . last . subject ) . not_to eq ( 'foo last sender' )
expect ( Ticket :: Article . where ( ticket : ticket ) . last . to ) . not_to eq ( system_address . email )
end
end
2021-08-16 11:34:05 +00:00
include_examples 'include ticket attachment'
2020-03-24 16:15:34 +00:00
end
2019-03-11 07:28:18 +00:00
end
2020-03-24 15:40:53 +00:00
context 'with pre condition current_user.id' do
let ( :perform ) do
{ 'ticket.title' = > { 'value' = > 'triggered' } }
end
let ( :user ) do
2020-06-19 09:17:18 +00:00
user = create ( :agent )
2020-03-24 15:40:53 +00:00
user . roles . first . groups << group
user
end
let ( :group ) { Group . first }
let ( :ticket ) do
create ( :ticket ,
title : 'Test Ticket' , group : group ,
owner_id : user . id , created_by_id : user . id , updated_by_id : user . id )
end
shared_examples 'successful trigger' do | attribute : |
let ( :attribute ) { attribute }
let ( :condition ) do
{ attribute = > { operator : 'is' , pre_condition : 'current_user.id' , value : '' , value_completion : '' } }
end
it " for #{ attribute } " do
ticket && trigger
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit } . to change { ticket . reload . title } . to ( 'triggered' )
2020-03-24 15:40:53 +00:00
end
end
it_behaves_like 'successful trigger' , attribute : 'ticket.updated_by_id'
it_behaves_like 'successful trigger' , attribute : 'ticket.owner_id'
end
2020-10-06 13:03:30 +00:00
describe 'Multi-trigger interactions:' do
let ( :ticket ) { create ( :ticket ) }
context 'cascading (i.e., trigger A satisfies trigger B satisfies trigger C)' do
subject! ( :triggers ) do
[
create ( :trigger , condition : initial_state , perform : first_change , name : 'A' ) ,
create ( :trigger , condition : first_change , perform : second_change , name : 'B' ) ,
create ( :trigger , condition : second_change , perform : third_change , name : 'C' )
]
end
context 'in a chain' do
let ( :initial_state ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'new' ) . id . to_s ,
}
}
end
let ( :first_change ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'open' ) . id . to_s ,
}
}
end
let ( :second_change ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'closed' ) . id . to_s ,
}
}
end
let ( :third_change ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'merged' ) . id . to_s ,
}
}
end
context 'in alphabetical order (by name)' do
it 'fires all triggers in sequence' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-10-06 13:03:30 +00:00
. to change { ticket . reload . state . name } . to ( 'merged' )
end
end
context 'out of alphabetical order (by name)' do
before do
triggers . first . update ( name : 'E' )
triggers . second . update ( name : 'F' )
triggers . third . update ( name : 'D' )
end
context 'with Setting ticket_trigger_recursive: true' do
before { Setting . set ( 'ticket_trigger_recursive' , true ) }
it 'evaluates triggers in sequence, then loops back to the start and re-evalutes skipped triggers' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-10-06 13:03:30 +00:00
. to change { ticket . reload . state . name } . to ( 'merged' )
end
end
context 'with Setting ticket_trigger_recursive: false' do
before { Setting . set ( 'ticket_trigger_recursive' , false ) }
it 'evaluates triggers in sequence, firing only the ones that match' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-10-06 13:03:30 +00:00
. to change { ticket . reload . state . name } . to ( 'closed' )
end
end
end
end
context 'in circular reference (i.e., trigger A satisfies trigger B satisfies trigger C satisfies trigger A...)' do
let ( :initial_state ) do
{
'ticket.priority_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: Priority . lookup ( name : '2 normal' ) . id . to_s ,
}
}
end
let ( :first_change ) do
{
'ticket.priority_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: Priority . lookup ( name : '3 high' ) . id . to_s ,
}
}
end
let ( :second_change ) do
{
'ticket.priority_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: Priority . lookup ( name : '1 low' ) . id . to_s ,
}
}
end
let ( :third_change ) do
{
'ticket.priority_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: Priority . lookup ( name : '2 normal' ) . id . to_s ,
}
}
end
context 'with Setting ticket_trigger_recursive: true' do
before { Setting . set ( 'ticket_trigger_recursive' , true ) }
it 'fires each trigger once, without being caught in an endless loop' do
2021-05-20 06:59:02 +00:00
expect { Timeout . timeout ( 2 ) { TransactionDispatcher . commit } }
2020-10-06 13:03:30 +00:00
. to not_change { ticket . reload . priority . name }
. and not_raise_error
end
end
context 'with Setting ticket_trigger_recursive: false' do
before { Setting . set ( 'ticket_trigger_recursive' , false ) }
it 'fires each trigger once, without being caught in an endless loop' do
2021-05-20 06:59:02 +00:00
expect { Timeout . timeout ( 2 ) { TransactionDispatcher . commit } }
2020-10-06 13:03:30 +00:00
. to not_change { ticket . reload . priority . name }
. and not_raise_error
end
end
end
end
context 'competing (i.e., trigger A un-satisfies trigger B)' do
subject! ( :triggers ) do
[
create ( :trigger , condition : initial_state , perform : change_a , name : 'A' ) ,
create ( :trigger , condition : initial_state , perform : change_b , name : 'B' )
]
end
let ( :initial_state ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'new' ) . id . to_s ,
}
}
end
let ( :change_a ) do
{
'ticket.state_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: State . lookup ( name : 'open' ) . id . to_s ,
}
}
end
let ( :change_b ) do
{
'ticket.priority_id' = > {
'operator' = > 'is' ,
'value' = > Ticket :: Priority . lookup ( name : '3 high' ) . id . to_s ,
}
}
end
it 'evaluates triggers in sequence, firing only the ones that match' do
2021-05-20 06:59:02 +00:00
expect { TransactionDispatcher . commit }
2020-10-06 13:03:30 +00:00
. to change { ticket . reload . state . name } . to ( 'open' )
. and not_change { ticket . reload . priority . name }
end
end
end
2022-01-20 10:07:12 +00:00
describe 'multiselect triggers' , db_strategy : :reset do
let ( :attribute_name ) { 'multiselect' }
let ( :condition ) do
{ " ticket. #{ attribute_name } " = > { 'operator' = > operator , 'value' = > trigger_values } }
end
let ( :perform ) do
{ 'article.note' = > { 'subject' = > 'Test subject note' , 'internal' = > 'true' , 'body' = > 'Test body note' } }
end
before do
create :object_manager_attribute_multiselect , name : attribute_name
ObjectManager :: Attribute . migration_execute
described_class . destroy_all # Default DB state includes three sample triggers
trigger # create subject trigger
end
context 'when ticket is updated with a multiselect trigger condition' , authenticated_as : :owner , db_strategy : :reset do
let ( :options ) do
{
a : 'a' ,
b : 'b' ,
c : 'c' ,
d : 'd' ,
e : 'e' ,
}
end
let ( :trigger_values ) { %w[ a b c ] }
let ( :group ) { create ( :group ) }
let ( :owner ) { create ( :admin , group_ids : [ group . id ] ) }
let! ( :ticket ) { create ( :ticket , group : group , ) }
before do
ticket . update_attribute ( attribute_name , ticket_multiselect_values )
end
shared_examples 'updating the ticket with the trigger condition' do
it 'updates the ticket with the trigger condition' do
expect { TransactionDispatcher . commit }
. to change ( Ticket :: Article , :count ) . by ( 1 )
end
end
shared_examples 'not updating the ticket with the trigger condition' do
it 'does not update the ticket with the trigger condition' do
expect { TransactionDispatcher . commit }
. to not_change ( Ticket :: Article , :count )
end
end
context " with 'contains all' used " do
let ( :operator ) { 'contains all' }
context 'when updated value is the same with trigger value' do
let ( :ticket_multiselect_values ) { trigger_values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value is different from the trigger value' do
let ( :ticket_multiselect_values ) { options . values - trigger_values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when no value is selected' do
let ( :ticket_multiselect_values ) { [ '-' ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when all value is selected' do
let ( :ticket_multiselect_values ) { options . values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value contains one of the trigger value' do
let ( :ticket_multiselect_values ) { [ trigger_values . first ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value does not contain one of the trigger value' do
let ( :ticket_multiselect_values ) { options . values - [ trigger_values . first ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
end
context " with 'contains one' used " do
let ( :operator ) { 'contains one' }
context 'when updated value is the same with trigger value' do
let ( :ticket_multiselect_values ) { trigger_values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value is different from the trigger value' do
let ( :ticket_multiselect_values ) { options . values - trigger_values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when no value is selected' do
let ( :ticket_multiselect_values ) { [ '-' ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when all value is selected' do
let ( :ticket_multiselect_values ) { options . values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value contains only one of the trigger value' do
let ( :ticket_multiselect_values ) { [ trigger_values . first ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value does not contain one of the trigger value' do
let ( :ticket_multiselect_values ) { options . values - [ trigger_values . first ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
end
context " with 'contains all not' used " do
let ( :operator ) { 'contains all not' }
context 'when updated value is the same with trigger value' do
let ( :ticket_multiselect_values ) { trigger_values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value is different from the trigger value' do
let ( :ticket_multiselect_values ) { options . values - trigger_values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when no value is selected' do
let ( :ticket_multiselect_values ) { [ '-' ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when all value is selected' do
let ( :ticket_multiselect_values ) { options . values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value contains only one of the trigger value' do
let ( :ticket_multiselect_values ) { [ trigger_values . first ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when updated value does not contain one of the trigger value' do
let ( :ticket_multiselect_values ) { options . values - [ trigger_values . first ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
end
context " with 'contains one not' used " do
let ( :operator ) { 'contains one not' }
context 'when updated value is the same with trigger value' do
let ( :ticket_multiselect_values ) { trigger_values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value is different from the trigger value' do
let ( :ticket_multiselect_values ) { options . values - trigger_values }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when no value is selected' do
let ( :ticket_multiselect_values ) { [ '-' ] }
it_behaves_like 'updating the ticket with the trigger condition'
end
context 'when all value is selected' do
let ( :ticket_multiselect_values ) { options . values }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value contains only one of the trigger value' do
let ( :ticket_multiselect_values ) { [ trigger_values . first ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
context 'when updated value does not contain one of the trigger value' do
let ( :ticket_multiselect_values ) { options . values - [ trigger_values . first ] }
it_behaves_like 'not updating the ticket with the trigger condition'
end
end
end
end
2019-02-26 11:00:46 +00:00
end