use strict; use warnings; use Setup; use Hydra::TaskDispatcher; use Prometheus::Tiny::Shared; use Test2::V0; use Test2::Tools::Mock qw(mock_obj); my $db = "bogus db"; my $prometheus = Prometheus::Tiny::Shared->new; sub make_noop_plugin { my ($name) = @_; my $plugin = { "name" => $name, }; my $mock_plugin = mock_obj $plugin => (); return $mock_plugin; } sub make_fake_event { my ($channel_name) = @_; my $event = { channel_name => $channel_name, called_with => [], }; my $mock_event = mock_obj $event => ( add => [ "execute" => sub { my ($self, $db, $plugin) = @_; push @{$self->{"called_with"}}, $plugin; } ] ); return $mock_event; } sub make_failing_event { my ($channel_name) = @_; my $event = { channel_name => $channel_name, called_with => [], }; my $mock_event = mock_obj $event => ( add => [ "execute" => sub { my ($self, $db, $plugin) = @_; push @{$self->{"called_with"}}, $plugin; die "Failing plugin." } ] ); return $mock_event; } sub make_fake_record { my %attrs = @_; my $record = { "attempts" => $attrs{"attempts"} || 0, "requeued" => 0, "deleted" => 0 }; my $mock_record = mock_obj $record => ( add => [ "delete" => sub { my ($self, $db, $plugin) = @_; $self->{"deleted"} = 1; }, "requeue" => sub { my ($self, $db, $plugin) = @_; $self->{"requeued"} = 1; } ] ); return $mock_record; } subtest "dispatch_event" => sub { subtest "every plugin gets called once, even if it fails all of them." => sub { my $plugins = [make_noop_plugin("bogus-1"), make_noop_plugin("bogus-2")]; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, $plugins); my $event = make_failing_event("bogus-channel"); $dispatcher->dispatch_event($event); is(@{$event->{"called_with"}}, 2, "Both plugins should be called"); my @expected_names = ( "bogus-1", "bogus-2" ); my @actual_names = sort( $event->{"called_with"}[0]->name, $event->{"called_with"}[1]->name ); is( \@actual_names, \@expected_names, "Both plugins should be executed, but not in any particular order." ); }; }; subtest "dispatch_task" => sub { subtest "every plugin gets called once" => sub { my $bogus_plugin = make_noop_plugin("bogus-1"); my $plugins = [$bogus_plugin, make_noop_plugin("bogus-2")]; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, $plugins); my $event = make_fake_event("bogus-channel"); my $task = Hydra::Task->new($event, ref $bogus_plugin); is($dispatcher->dispatch_task($task), 1, "Calling dispatch_task returns truthy."); is(@{$event->{"called_with"}}, 1, "Just one plugin should be called"); is( $event->{"called_with"}[0]->name, "bogus-1", "Just bogus-1 should be executed." ); }; subtest "a task with an invalid plugin is not fatal" => sub { my $bogus_plugin = make_noop_plugin("bogus-1"); my $plugins = [$bogus_plugin, make_noop_plugin("bogus-2")]; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, $plugins); my $event = make_fake_event("bogus-channel"); my $task = Hydra::Task->new($event, "this-plugin-does-not-exist"); is($dispatcher->dispatch_task($task), 0, "Calling dispatch_task returns falsey."); is(@{$event->{"called_with"}}, 0, "No plugins are called"); }; subtest "a failed run without a record saves the task for later" => sub { my $db = "bogus db"; my $record = make_fake_record(); my $bogus_plugin = make_noop_plugin("bogus-1"); my $task = { "event" => make_failing_event("fail-event"), "plugin_name" => ref $bogus_plugin, "record" => undef, }; my $save_hook_called = 0; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, [$bogus_plugin], sub { $save_hook_called = 1; } ); $dispatcher->dispatch_task($task); is($save_hook_called, 1, "The record was requeued with the store hook."); }; subtest "a successful run from a record deletes the record" => sub { my $db = "bogus db"; my $record = make_fake_record(); my $bogus_plugin = make_noop_plugin("bogus-1"); my $task = { "event" => make_fake_event("success-event"), "plugin_name" => ref $bogus_plugin, "record" => $record, }; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, [$bogus_plugin]); $dispatcher->dispatch_task($task); is($record->{"deleted"}, 1, "The record was deleted."); }; subtest "a failed run from a record re-queues the task" => sub { my $db = "bogus db"; my $record = make_fake_record(); my $bogus_plugin = make_noop_plugin("bogus-1"); my $task = { "event" => make_failing_event("fail-event"), "plugin_name" => ref $bogus_plugin, "record" => $record, }; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, [$bogus_plugin]); $dispatcher->dispatch_task($task); is($record->{"requeued"}, 1, "The record was requeued."); }; subtest "a failed run from a record with a lot of attempts deletes the task" => sub { my $db = "bogus db"; my $record = make_fake_record(attempts => 101); my $bogus_plugin = make_noop_plugin("bogus-1"); my $task = { "event" => make_failing_event("fail-event"), "plugin_name" => ref $bogus_plugin, "record" => $record, }; my $dispatcher = Hydra::TaskDispatcher->new($db, $prometheus, [$bogus_plugin]); $dispatcher->dispatch_task($task); is($record->{"deleted"}, 1, "The record was deleted."); }; }; done_testing;