1# -- 2# Copyright (C) 2001-2020 OTRS AG, https://otrs.com/ 3# -- 4# This software comes with ABSOLUTELY NO WARRANTY. For details, see 5# the enclosed file COPYING for license information (GPL). If you 6# did not receive this file, see https://www.gnu.org/licenses/gpl-3.0.txt. 7# -- 8 9use strict; 10use warnings; 11use utf8; 12 13use vars (qw($Self)); 14 15use Kernel::System::VariableCheck qw(:all); 16 17# get needed objects 18my $DynamicFieldObject = $Kernel::OM->Get('Kernel::System::DynamicField'); 19my $ActivityObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::Activity'); 20my $ActivityDialogObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::ActivityDialog'); 21my $ProcessObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::Process'); 22my $TransitionObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::Transition'); 23my $TransitionActionObject = $Kernel::OM->Get('Kernel::System::ProcessManagement::DB::TransitionAction'); 24my $YAMLObject = $Kernel::OM->Get('Kernel::System::YAML'); 25 26# get helper object 27$Kernel::OM->ObjectParamAdd( 28 'Kernel::System::UnitTest::Helper' => { 29 RestoreDatabase => 1, 30 }, 31); 32my $Helper = $Kernel::OM->Get('Kernel::System::UnitTest::Helper'); 33 34# define needed variables 35my $RandomID = $Helper->GetRandomID(); 36my $Home = $Kernel::OM->Get('Kernel::Config')->Get('Home'); 37my $UserID = 1; 38 39# get a list of current processes and it parts 40my $OriginalProcessList = $ProcessObject->ProcessList( 41 UserID => 1, 42); 43my $OriginalActivityList = $ActivityObject->ActivityList( 44 UserID => 1, 45); 46my $OriginalActivityDialogList = $ActivityDialogObject->ActivityDialogList( 47 UserID => 1, 48); 49my $OriginalTransitionList = $TransitionObject->TransitionList( 50 UserID => 1, 51); 52my $OriginalTransitionActionList = $TransitionActionObject->TransitionActionList( 53 UserID => 1, 54); 55 56my %PartNameMap = ( 57 Activity => 'Activities', 58 ActivityDialog => 'ActivityDialogs', 59 Transition => 'Transitions', 60 TransitionAction => 'TransitionActions' 61); 62 63# this function appends a Random number to all process parts so they can be located later as they 64# will be different form any other test and from the ones already stored in the system 65my $UpdateNames = sub { 66 my %Param = @_; 67 68 my $ProcessData = $Param{ProcessData}; 69 my $RandomID = $Param{RandomID}; 70 71 $ProcessData->{Process}->{Name} = $ProcessData->{Process}->{Name} . $RandomID; 72 73 my $Counter; 74 75 for my $PartName (qw(Activity ActivityDialog Transition TransitionAction)) { 76 $Counter = 1; 77 for my $PartEntityID ( sort keys %{ $ProcessData->{ $PartNameMap{$PartName} } } ) { 78 $ProcessData->{ $PartNameMap{$PartName} }->{$PartEntityID}->{Name} 79 .= " $Counter-$RandomID"; 80 $Counter++; 81 } 82 } 83 84 return $ProcessData; 85}; 86 87# this function check that the process and its parts where imported correctly, normally the 88# EntityIDs changes for the imported processes then is needed to locate them by its name 89my $CheckProcess = sub { 90 my %Param = @_; 91 92 my $ProcessData = $Param{ProcessData}; 93 my $ProcessID = $Param{ProcessID}; 94 95 my $Process = $ProcessObject->ProcessGet( 96 ID => $ProcessID, 97 UserID => $UserID, 98 ); 99 100 # get all process parts 101 my $ActivityListGet = $ActivityObject->ActivityListGet( UserID => $UserID ); 102 my $ActivityDialogListGet = $ActivityDialogObject->ActivityDialogListGet( UserID => $UserID ); 103 my $TransitionListGet = $TransitionObject->TransitionListGet( UserID => $UserID ); 104 my $TransitionActionListGet = $TransitionActionObject->TransitionActionListGet( UserID => $UserID ); 105 106 # check process start activity and start activity dialog 107 for my $PartName (qw(Activity ActivityDialog)) { 108 my $OriginalPartEntityID = $ProcessData->{Process}->{Config}->{"Start$PartName"} || ''; 109 my $PartObject = $ActivityObject; 110 if ( $PartName eq 'ActivityDialog' ) { 111 $PartObject = $ActivityDialogObject; 112 } 113 my $PartGetFunction = $PartName . 'Get'; 114 if ($OriginalPartEntityID) { 115 my $Part = $PartObject->$PartGetFunction( 116 EntityID => $Process->{Config}->{"Start$PartName"}, 117 UserID => 1, 118 ); 119 $Self->Is( 120 $Part->{Name}, 121 $ProcessData->{ $PartNameMap{$PartName} }->{$OriginalPartEntityID}->{Name}, 122 "ProcessImport() $Param{TestName} - Start $PartName name check:", 123 ); 124 } 125 } 126 127 # check layout (check for left and right values) 128 for my $OriginalActivityEntityID ( sort keys %{ $ProcessData->{Process}->{Layout} } ) { 129 my $ActivityName = $ProcessData->{Activities}->{$OriginalActivityEntityID}->{Name}; 130 131 ACTIVITY: 132 for my $Activity ( @{$ActivityListGet} ) { 133 next ACTIVITY if $Activity->{Name} ne $ActivityName; 134 $Self->IsDeeply( 135 $Process->{Layout}->{ $Activity->{EntityID} }, 136 $ProcessData->{Process}->{Layout}->{$OriginalActivityEntityID}, 137 "ProcessImport() $Param{TestName} - Layout Activity ($Activity->{EntityID}) " 138 . "content check:", 139 ); 140 last ACTIVITY; 141 } 142 } 143 144 # check path 145 # a typical path looks like: 146 # Path { 147 # A1 => { 148 # T1 => { 149 # ActivityEntityID => A2, 150 # TransitionAction => [TA1, TA2,], 151 # }, 152 # }, 153 # #... 154 # }, 155 # locate the activity and its name 156 for my $OriginalActivityEntityID ( sort keys %{ $ProcessData->{Process}->{Config}->{Path} } ) { 157 my $ActivityName = $ProcessData->{Activities}->{$OriginalActivityEntityID}->{Name}; 158 159 # search added activities for the activity name 160 ACTIVITY: 161 for my $Activity ( @{$ActivityListGet} ) { 162 next ACTIVITY if $Activity->{Name} ne $ActivityName; 163 164 # locate the transition and its name 165 my $OriginalTransitions = $ProcessData->{Process}->{Config}->{Path}->{$OriginalActivityEntityID}; 166 for my $OriginalTransitionEntityID ( sort keys %{$OriginalTransitions} ) { 167 my $TransitionName = $ProcessData->{Transitions}->{$OriginalTransitionEntityID}->{Name}; 168 169 # search added translation for the transition name 170 TRANSITION: 171 for my $Transition ( @{$TransitionListGet} ) { 172 next TRANSITION if $Transition->{Name} ne $TransitionName; 173 174 # locate the destination activity and its name 175 my $OriginalDestinationActivityEntityID 176 = $OriginalTransitions->{$OriginalTransitionEntityID}->{ActivityEntityID}; 177 my $DestinationActivityName = $ProcessData->{Activities}->{$OriginalDestinationActivityEntityID} 178 ->{Name}; 179 180 # search added activities for the destination activity name 181 DESTINATIONACTIVITY: 182 for my $DestinationActivity ( @{$ActivityListGet} ) { 183 next DESTINATIONACTIVITY 184 if $DestinationActivity->{Name} ne $DestinationActivityName; 185 186 # test if entities match 187 $Self->Is( 188 $Process->{Config}->{Path}->{ $Activity->{EntityID} } 189 ->{ $Transition->{EntityID} }->{ActivityEntityID}, 190 $DestinationActivity->{EntityID}, 191 "ProcessImport() $Param{TestName} - Path Activity " 192 . "($Activity->{EntityID}) -> Transition ($Transition->{EntityID}) " 193 . "-> ActivityEntity value check:", 194 ); 195 last DESTINATIONACTIVITY; 196 } 197 198 # locate each transition action and its names 199 my $OriginalTransitionActions 200 = $OriginalTransitions->{$OriginalTransitionEntityID}->{TransitionAction}; 201 my @ExpectedTrasitionActionEntityIDs; 202 for my $OriginalTransitionActionEntityID ( @{$OriginalTransitionActions} ) { 203 my $TransitionActionName = $ProcessData->{TransitionActions} 204 ->{$OriginalTransitionActionEntityID}->{Name}; 205 206 # search added transition actions for the transition name and remember it 207 # in the same order 208 TRANSITIONACTION: 209 for my $TransitionAction ( @{$TransitionActionListGet} ) { 210 next TRANSITIONACTION 211 if $TransitionAction->{Name} ne $TransitionActionName; 212 push @ExpectedTrasitionActionEntityIDs, $TransitionAction->{EntityID}; 213 last TRANSITIONACTION; 214 } 215 } 216 217 # test if transition actions entities match 218 my $CurrentTransitionActions = $Process->{Config}->{Path}->{ $Activity->{EntityID} } 219 ->{ $Transition->{EntityID} }->{TransitionAction} || []; 220 $Self->IsDeeply( 221 $CurrentTransitionActions, 222 \@ExpectedTrasitionActionEntityIDs, 223 "ProcessImport() $Param{TestName} - Path Activity ($Activity->{EntityID}) " 224 . "-> Transition ($Transition->{EntityID}) -> TransitionAction " 225 . "value check:", 226 ); 227 last TRANSITION; 228 } 229 } 230 last ACTIVITY; 231 } 232 } 233 234 # now check each part of the process 235 # check activities 236 # locate activity and its name 237 for my $OriginalActivityEntityID ( sort keys %{ $ProcessData->{Activities} } ) { 238 my $OriginalActivityName = $ProcessData->{Activities}->{$OriginalActivityEntityID}->{Name}; 239 240 # search added activities for the activity name 241 ACTIVITY: 242 for my $Activity ( @{$ActivityListGet} ) { 243 next ACTIVITY if $Activity->{Name} ne $OriginalActivityName; 244 245 # locate each activity dialog and its names 246 my $OriginalActivityDialogs = $ProcessData->{Activities}->{$OriginalActivityEntityID}->{Config} 247 ->{ActivityDialog}; 248 my %ExpectedActivityDialogEntityIDs; 249 for my $OrderKey ( sort keys %{$OriginalActivityDialogs} ) { 250 my $ActivityDialogName = $ProcessData->{ActivityDialogs}->{ $OriginalActivityDialogs->{$OrderKey} } 251 ->{Name}; 252 253 # search added activity dialogs for the activity dialog name and remember it 254 # in the same order 255 ACTIVITYDIALOG: 256 for my $ActivityDialog ( @{$ActivityDialogListGet} ) { 257 next ACTIVITYDIALOG if $ActivityDialog->{Name} ne $ActivityDialogName; 258 $ExpectedActivityDialogEntityIDs{$OrderKey} = $ActivityDialog->{EntityID}; 259 last ACTIVITYDIALOG; 260 } 261 } 262 263 # test if activity dialog entities match 264 my $CurrentActivityDialogs = $Activity->{Config}->{ActivityDialog} || {}; 265 $Self->IsDeeply( 266 $CurrentActivityDialogs, 267 \%ExpectedActivityDialogEntityIDs, 268 "ProcessImport() $Param{TestName} - Activity ($Activity->{EntityID}) -> Config " 269 . "-> ActivityDialog value check:", 270 ); 271 272 last ACTIVITY; 273 } 274 } 275 276 # check the rest of the process parts 277 my %PartListGetMap = ( 278 ActivityDialog => $ActivityDialogListGet, 279 Transition => $TransitionListGet, 280 TransitionAction => $TransitionActionListGet, 281 ); 282 for my $PartName (qw(ActivityDialog Transition TransitionAction)) { 283 284 # locate process part and its name 285 for my $OriginalPartEntityID ( sort keys %{ $ProcessData->{ $PartNameMap{$PartName} } } ) { 286 my $OriginalPartName = $ProcessData->{ $PartNameMap{$PartName} }->{$OriginalPartEntityID}->{Name}; 287 288 # search added parts for the part name 289 PART: 290 for my $Part ( @{ $PartListGetMap{$PartName} } ) { 291 next PART if $Part->{Name} ne $OriginalPartName; 292 293 # test part config 294 $Self->IsDeeply( 295 $Part->{Config}, 296 $ProcessData->{ $PartNameMap{$PartName} }->{$OriginalPartEntityID}->{Config}, 297 "ProcessImport() $Param{TestName} - $PartName ($Part->{EntityID}) " 298 . "-> Config value check:", 299 ); 300 last PART; 301 } 302 } 303 } 304}; 305 306my @AddedFieldIDs; 307 308# this function creates missing dynamic fields required by imported processes and store its ID for 309# removal on test end 310my $CreateDyanmicFields = sub { 311 my %Param = @_; 312 313 my $ProcessData = $Param{ProcessData}; 314 315 # collect all used fields 316 my @UsedDynamicFields; 317 for my $ActivityDialog ( sort keys %{ $ProcessData->{ActivityDialogs} } ) { 318 for my $FieldName ( 319 sort 320 keys %{ $ProcessData->{ActivityDialogs}->{$ActivityDialog}->{Config}->{Fields} } 321 ) 322 { 323 if ( $FieldName =~ s{DynamicField_(\w+)}{$1}xms ) { 324 push @UsedDynamicFields, $FieldName; 325 } 326 } 327 } 328 329 # get all present dynamic fields and check if the fields used in the config are beyond them 330 my $DynamicFieldList = $DynamicFieldObject->DynamicFieldList( 331 ResultType => 'HASH', 332 ); 333 my @PresentDynamicFieldNames = values %{$DynamicFieldList}; 334 335 my @MissingDynamicFieldNames; 336 for my $UsedDynamicFieldName (@UsedDynamicFields) { 337 if ( !grep { $_ eq $UsedDynamicFieldName } @PresentDynamicFieldNames ) { 338 push @MissingDynamicFieldNames, $UsedDynamicFieldName; 339 } 340 } 341 342 my %NewAddedDynamicFields; 343 344 DYNAMICFIELDNAME: 345 for my $DynamicFieldName (@MissingDynamicFieldNames) { 346 next DYNAMICFIELDNAME if $NewAddedDynamicFields{$DynamicFieldName}; 347 my $ID = $DynamicFieldObject->DynamicFieldAdd( 348 InternalField => 0, 349 Name => $DynamicFieldName, 350 Label => $DynamicFieldName, 351 FieldOrder => 10000, 352 FieldType => 'Text', 353 ObjectType => 'Ticket', 354 Config => { 355 DefaultValue => '', 356 }, 357 Reorder => 0, 358 ValidID => 1, 359 UserID => $UserID, 360 ); 361 if ($ID) { 362 push @AddedFieldIDs, $ID; 363 $NewAddedDynamicFields{$DynamicFieldName} = 1; 364 } 365 $Self->True( 366 $ID, 367 "DynamicField() $Param{TestName} - Added needed DynamicField ($DynamicFieldName) " 368 . "with true", 369 ); 370 } 371}; 372 373my @Tests = ( 374 { 375 Name => 'Missing parameters', 376 Config => {}, 377 Success => 0, 378 }, 379 { 380 Name => 'Missing UserID', 381 Config => {}, 382 ProcessFile => 'EmptyProcess.yml', 383 Success => 0, 384 }, 385 { 386 Name => 'Missing Content', 387 Config => { 388 UserID => $UserID, 389 }, 390 Success => 0, 391 }, 392 { 393 Name => 'Empty Process', 394 Config => { 395 UserID => $UserID, 396 }, 397 ProcessFile => 'EmptyProcess.yml', 398 Success => 1, 399 }, 400 { 401 Name => 'Complex 1', 402 Config => { 403 UserID => $UserID, 404 }, 405 ProcessFile => 'Complex1.yml', 406 Success => 1, 407 }, 408 { 409 Name => 'Complex 2 Missing DF', 410 Config => { 411 UserID => $UserID, 412 }, 413 ProcessFile => 'Complex2.yml', 414 Success => 0, 415 }, 416 { 417 Name => 'Complex 2 Creating DFs', 418 Config => { 419 UserID => $UserID, 420 }, 421 ProcessFile => 'Complex2.yml', 422 CreateDynamicFields => 1, 423 Success => 1, 424 }, 425 { 426 Name => 'Complex 3 Creating DFs', 427 Config => { 428 UserID => $UserID, 429 }, 430 ProcessFile => 'Complex3.yml', 431 CreateDynamicFields => 1, 432 Success => 1, 433 }, 434 { 435 Name => 'Complex 4', 436 Config => { 437 UserID => $UserID, 438 }, 439 ProcessFile => 'Complex4.yml', 440 Success => 1, 441 }, 442 { 443 Name => 'Complex 5 Creating DFs', 444 Config => { 445 UserID => $UserID, 446 }, 447 ProcessFile => 'Complex5.yml', 448 CreateDynamicFields => 1, 449 Success => 1, 450 }, 451 { 452 Name => 'Complex 6 Creating DFs', 453 Config => { 454 UserID => $UserID, 455 }, 456 ProcessFile => 'Complex6.yml', 457 CreateDynamicFields => 1, 458 Success => 1, 459 }, 460 { 461 Name => 'GUID 1 OverwriteExistingEntities', 462 Config => { 463 UserID => $UserID, 464 OverwriteExistingEntities => 1 465 }, 466 ProcessFile => 'GUID1.yml', 467 CreateDynamicFields => 1, 468 Success => 1, 469 }, 470 { 471 Name => 'GUID 1 UPDATED OverwriteExistingEntities', 472 Config => { 473 UserID => $UserID, 474 OverwriteExistingEntities => 1 475 }, 476 ProcessFile => 'GUID1Updated.yml', 477 CreateDynamicFields => 1, 478 Success => 1, 479 }, 480); 481 482for my $Test (@Tests) { 483 484 my $ProcessData; 485 486 # read process for YAML file if needed 487 my $FileRef; 488 if ( $Test->{ProcessFile} ) { 489 $FileRef = $Kernel::OM->Get('Kernel::System::Main')->FileRead( 490 Location => $Home . '/scripts/test/sample/ProcessManagement/' . $Test->{ProcessFile}, 491 ); 492 my $RandomID = $Helper->GetRandomID(); 493 494 # convert process to Perl for easy handling 495 $ProcessData = $YAMLObject->Load( Data => $$FileRef ); 496 } 497 498 # update all process names for easy search 499 if ( IsHashRefWithData($ProcessData) ) { 500 $ProcessData = $UpdateNames->( 501 ProcessData => $ProcessData, 502 RandomID => $RandomID, 503 ); 504 } 505 506 # convert process back to YAML and set it as part of the config 507 my $Content = $YAMLObject->Dump( Data => $ProcessData ); 508 $Test->{Config}->{Content} = $Content; 509 510 # create missing dynamic fields for the process if needed 511 if ( $Test->{CreateDynamicFields} ) { 512 $CreateDyanmicFields->( 513 ProcessData => $ProcessData, 514 TestName => $Test->{Name}, 515 ); 516 } 517 518 # call import function 519 my %ProcessImport = $ProcessObject->ProcessImport( %{ $Test->{Config} } ); 520 521 if ( $Test->{Success} ) { 522 $Self->True( 523 $ProcessImport{Success}, 524 "ProcessImport() $Test->{Name} - return value with true", 525 ); 526 527 # get CurrentProcessID 528 my $CurrentProcessList = $ProcessObject->ProcessListGet( 529 UserID => 1, 530 ); 531 532 my @ProcessTest = grep { $_->{Name} eq $ProcessData->{Process}->{Name} } @{$CurrentProcessList}; 533 my $CurrentProcessID = $ProcessTest[0]->{ID}; 534 535 $Self->IsNot( 536 $CurrentProcessID, 537 undef, 538 "ProcessImport() $Test->{Name} - Process found by name, ProcessID must not be undef", 539 ); 540 541 # run matching tests 542 $CheckProcess->( 543 ProcessData => $ProcessData, 544 ProcessID => $CurrentProcessID, 545 TestName => $Test->{Name}, 546 ); 547 } 548 else { 549 $Self->False( 550 $ProcessImport{Success}, 551 "ProcessImport() $Test->{Name} - return value with false", 552 ); 553 } 554} 555 556# cleanup is done by RestoreDatabase 557 5581; 559