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