diff --git a/Koha/Reports.pm b/Koha/Reports.pm index 061a6f5bed..beb7ccd8f9 100644 --- a/Koha/Reports.pm +++ b/Koha/Reports.pm @@ -79,9 +79,8 @@ sub running { my $sql = 'SELECT info FROM information_schema.processlist WHERE ' . join( ' AND ', @where ); - my $schema = Koha::Database->new->schema; my $rows; - eval { $rows = $schema->storage->dbh->selectall_arrayref( $sql, { Slice => {} }, @binds ); }; + eval { $rows = $class->_processlist_rows( $sql, @binds ); }; if ($@) { Koha::Logger->get->warn("Koha::Reports->running: unable to query information_schema.processlist: $@"); $rows = []; @@ -95,6 +94,11 @@ sub running { return $class->search( { id => { -in => [ keys %report_ids ] } } ); } +sub _processlist_rows { + my ( $class, $sql, @binds ) = @_; + return Koha::Database->new->schema->storage->dbh->selectall_arrayref( $sql, { Slice => {} }, @binds ); +} + =head3 _type Returns name of corresponding DBIC resultset diff --git a/t/db_dependent/Koha/Reports.t b/t/db_dependent/Koha/Reports.t index 2e83400f33..462d2232d3 100755 --- a/t/db_dependent/Koha/Reports.t +++ b/t/db_dependent/Koha/Reports.t @@ -17,8 +17,9 @@ use Modern::Perl; +use Test::MockModule; use Test::NoWarnings; -use Test::More tests => 10; +use Test::More tests => 11; use Koha::Report; use Koha::Reports; @@ -213,4 +214,66 @@ subtest 'apply_execution_time_limit' => sub { ); }; +subtest 'running' => sub { + plan tests => 7; + + my $running = Koha::Reports->running; + isa_ok( $running, 'Koha::Reports', 'running() returns a Koha::Reports resultset' ); + + # No saved-report SQL is in flight in the test process; processlist may show + # this very test connection but its current statement carries no saved_sql.id + # marker, so the result must be empty. + is( Koha::Reports->running->count, 0, 'no running reports => empty resultset' ); + + is( + Koha::Reports->running( { user_id => 999_999_999 } )->count, 0, + 'unknown user_id => empty resultset' + ); + is( + Koha::Reports->running( { report_id => 999_999_999 } )->count, 0, + 'unknown report_id => empty resultset' + ); + + # Capture the SQL bindings and feed synthetic rows back; this exercises the + # WHERE-clause assembly and the saved_sql.id parser without relying on a + # real long-running query in the database. + my $reports_mock = Test::MockModule->new('Koha::Reports'); + my @captured_binds; + my $synthetic_rows = []; + $reports_mock->mock( + '_processlist_rows', + sub { + my ( $class, $sql, @binds ) = @_; + push @captured_binds, [@binds]; + return $synthetic_rows; + } + ); + + my $report_a = $builder->build_object( { class => 'Koha::Reports' } ); + my $report_b = $builder->build_object( { class => 'Koha::Reports' } ); + my ( $a_id, $b_id ) = ( $report_a->id, $report_b->id ); + + $synthetic_rows = [ + { info => "SELECT 1 /* { saved_sql.id: $a_id } { user_id: 17 } */" }, + { info => "SELECT 1 /* { saved_sql.id: $b_id } { user_id: 18 } */" }, + { info => "SELECT 1 /* no marker, should be ignored */" }, + ]; + is( Koha::Reports->running->count, 2, 'parses saved_sql.id markers and returns matching reports' ); + + @captured_binds = (); + Koha::Reports->running( { user_id => 17 } ); + is_deeply( + $captured_binds[0], + [ 'Sleep', '%saved_sql.id:%', '%{ user_id: 17 }%' ], + 'user_id contributes a parameterised LIKE bind' + ); + + # Graceful degradation when the underlying DB call dies (eg. PROCESS denied). + $reports_mock->mock( '_processlist_rows', sub { die "Access denied for user\n"; } ); + is( + Koha::Reports->running->count, 0, + 'DB failure => empty resultset (caller is not aborted)' + ); +}; + $schema->storage->txn_rollback;