1#!/usr/local/bin/perl 2use strict; 3use warnings; 4 5# Core modules 6use FindBin qw( $Bin ); 7 8# CPAN modules 9use Test::More; 10use Test::Deep; 11use Test::Exception; 12use Data::UUID; 13use Try::Tiny; 14 15#use OpenXPKI::Debug; $OpenXPKI::Debug::LEVEL{'OpenXPKI::Server::Workflow::Persister.*'} = 32; 16 17# Project modules 18use lib "$Bin/../lib"; 19use lib "$Bin"; 20use OpenXPKI::Server::Context qw( CTX ); 21use OpenXPKI::Test; 22 23try { 24 require OpenXPKI::Server::Workflow::Persister::Archiver; 25 plan tests => 5; 26} 27catch { 28 plan skip_all => "persister 'Archiver' no available"; 29}; 30 31my $wf_def = " 32head: 33 prefix: testwf 34 persister: Archiver 35 36state: 37 INITIAL: 38 action: noop > AUTO 39 40 AUTO: 41 action: set_context set_attr > LOITER 42 autorun: 1 43 44 LOITER: 45 action: noop > SUCCESS 46 47action: 48 noop: 49 class: OpenXPKI::Server::Workflow::Activity::Noop 50 51 set_context: 52 class: OpenXPKI::Server::Workflow::Activity::Tools::SetContext 53 param: 54 env: sky 55 temp: -10 56 vehicle: plane 57 58 set_attr: 59 class: OpenXPKI::Server::Workflow::Activity::Tools::SetAttribute 60 param: 61 creator: dummy 62 color: blue 63 hairstyle: bald 64 shoesize: 10 65 66acl: 67 MyFairyKing: 68 creator: any 69"; 70 71# 72# Fail on wrong persister arguments 73# 74throws_ok { 75 my $oxitest = OpenXPKI::Test->new( 76 with => [ qw( TestRealms ) ], 77 also_init => "workflow_factory", 78 add_config => { 79 "realm.alpha.workflow.persister.Archiver" => " 80 class: OpenXPKI::Server::Workflow::Persister::Archiver 81 cleanup_defaults: 82 crash: test dummies 83 ", 84 }, 85 ); 86} qr/defaults must contain/, "fail on wrong arguments for persister defaults"; 87 88sub items_ok($@) { 89 my $testname = shift; 90 my %args = @_; 91 my $config = $args{config}; 92 my $items = $args{expected}; 93 my $force_failure = $args{force_failure}; 94 my $fields = $config->{workflow}->{field} // {}; 95 my $attributes = $config->{workflow}->{attribute} // {}; 96 97 my $persister = { 98 class => 'OpenXPKI::Server::Workflow::Persister::Archiver', 99 %{ $config->{persister} // {} }, 100 }; 101 102 subtest $testname => sub { 103 my $workflow_type = "TESTWORKFLOW".int(rand(2**32)); 104 105 # 106 # Setup test context 107 # 108 my $oxitest = OpenXPKI::Test->new( 109 with => [ qw( TestRealms ) ], 110 also_init => "workflow_factory", 111 add_config => { 112 "realm.alpha.workflow.persister.Archiver" => $persister, 113 "realm.alpha.workflow.def.$workflow_type" => $wf_def, 114 "realm.alpha.workflow.def.$workflow_type.field" => $fields, 115 "realm.alpha.workflow.def.$workflow_type.attribute" => $attributes, 116 "realm.alpha.workflow.def.$workflow_type.state.SUCCESS.output" => ($config->{workflow}->{success_output} // []), 117 "realm.alpha.workflow.def.$workflow_type.state.FAILURE.output" => ($config->{workflow}->{failure_output} // []), 118 }, 119 ); 120 121 $oxitest->session->data->role("MyFairyKing"); 122 123 my $wf1; 124 125 # Create workflow 126 lives_and { 127 $wf1 = CTX('workflow_factory')->get_factory->create_workflow($workflow_type); 128 ok ref $wf1; 129 } "create test workflow" or die("Could not create workflow"); 130 131 # Run workflow action to 132 # - set fields and attributes and 133 # - trigger cleanup (only if $fail == 0) 134 lives_ok { 135 $wf1->execute_action("testwf_noop"); 136 } "execute workflow action"; 137 138 if ($force_failure) { 139 note "manually failing workflow"; 140 $wf1->set_failed('entangled something', 'we just saw this...'); 141 } 142 else { 143 lives_ok { 144 $wf1->execute_action("testwf_noop"); 145 } "execute workflow action"; 146 } 147 148 my $wf2; 149 lives_and { 150 $wf2 = CTX('workflow_factory')->get_factory->fetch_workflow($workflow_type, $wf1->id); 151 ok ref $wf2; 152 } "refetch workflow from database"; 153 154 cmp_deeply $wf2->context->param, { 155 %{ $items->{field} }, 156 creator => ignore(), 157 workflow_id => ignore() 158 }, "correct context items"; 159 160 cmp_deeply $wf2->attrib, { %{$items->{attribute}}, creator => 'dummy' }, 161 "correct attributes"; 162 163 my $history = [ map { $_->action } $wf2->get_history ]; 164 cmp_bag $history, $items->{history}, 165 "correct history" 166 or diag(join "", map { sprintf "[%s] %s --> ", $_->state, $_->action } sort { $a->date->epoch <=> $b->date->epoch } $wf2->get_history); 167 168 $oxitest->dbi->delete_and_commit(from => 'workflow', where => { workflow_type => $workflow_type }); 169 $oxitest->dbi->delete_and_commit(from => 'workflow_attributes', where => { workflow_id => $wf1->id }); 170 }; 171} 172 173# 174# Tests 175# 176 177# Input for each test: 178# 179# Fields: 180# env: sky 181# temp: -10 # part of the 'output' of state SUCCESS 182# vehicle: plane # explicitely configured as "cleanup: none" 183# 184# Attributes: 185# color: blue 186# hairstyle: bald 187# shoesize: 10 # explicitely configured as "cleanup: none" 188 189# ============================================================================= 190# Standard cleanup 191# 192items_ok "standard cleanup: internal defaults", 193 config => { 194 # Internal persister defaults: 195 # field: finished 196 # attribute: none 197 # history: archived 198 workflow => { 199 field => { 'vehicle' => { cleanup => 'none' } }, 200 attribute => { 'color' => { cleanup => 'finished' } }, 201 success_output => [ 'temp' ], 202 }, 203 }, 204 expected => { 205 field => { 206 'vehicle' => 'plane', 207 'temp' => -10, 208 }, 209 attribute => { 210 'shoesize' => 10, 211 'hairstyle' => 'bald', 212 }, 213 history => [ qw( testwf_noop testwf_set_context testwf_set_attr testwf_noop ) ], 214 }; 215 216items_ok "standard cleanup: explicit persister settings", # ... merged with internal defaults 217 config => { 218 # Internal persister defaults: 219 # field: finished 220 persister => { 221 cleanup_defaults => { 222 attribute => 'finished', 223 history => 'finished', 224 }, 225 }, 226 workflow => { 227 attribute => { 'shoesize' => { cleanup => 'none' } }, 228 }, 229 }, 230 expected => { 231 field => { }, 232 attribute => { 233 'shoesize' => 10, 234 }, 235 history => [ qw( ) ], 236 }; 237 238# ============================================================================= 239# Cleanup upon forced failure 240# 241 242items_ok "forced failure: internal defaults", 243 force_failure => 1, 244 config => { 245 # Internal persister defaults: 246 # field: keep 247 # attribute: drop 248 # history: keep 249 workflow => { 250 field => { 'temp' => { onfail => 'drop' } }, 251 attribute => { 'shoesize' => { onfail => 'keep' } }, 252 }, 253 }, 254 expected => { 255 field => { 256 'env' => 'sky', 257 'vehicle' => 'plane', 258 }, 259 attribute => { 260 'shoesize' => 10, 261 }, 262 # duplicate 'testwf_set_attr' as state FAILURE also logs it as action 263 history => [ qw( testwf_noop testwf_set_context testwf_set_attr testwf_set_attr ) ], 264 }; 265 266items_ok "forced failure: explicit persister settings", 267 force_failure => 1, 268 config => { 269 # Internal persister defaults: 270 # attribute: drop 271 persister => { 272 onfail_defaults => { 273 field => 'drop', 274 history => 'drop', 275 }, 276 }, 277 workflow => { 278 field => { 'env' => { onfail => 'keep' } }, 279 failure_output => [ 'vehicle' ], 280 }, 281 }, 282 expected => { 283 field => { 284 'env' => 'sky', 285 'vehicle' => 'plane', 286 }, 287 attribute => { }, 288 # state FAILURE also logs 'testwf_set_attr' as action 289 history => [ qw( testwf_set_attr ) ], 290 }; 291