From c8f0f064d617e0f938f7bd16cf4f82622b3cb84d Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Mon, 27 Apr 2026 17:47:57 +0100 Subject: [PATCH] Bug 41918: (QA follow-up) Add unit tests for Koha::Reports->running Adds a 'running' subtest covering the new method: - Returns a Koha::Reports resultset (object identity, not opaque ids). - Returns an empty resultset when nothing is in flight, and when the user_id / report_id filter does not match any process. - Parses saved_sql.id markers out of synthetic processlist rows and resolves them to real Koha::Report objects, ignoring rows without the marker. - Sends parameterised LIKE binds for user_id (proves the WHERE clause is built without string interpolation). - Falls back to an empty resultset and does not propagate the exception when the underlying selectall_arrayref call dies, eg. when the database user lacks PROCESS privilege. To make the failure path mockable without trying to mock DBI::db (which is not loadable as a separate file), the dbh selectall is extracted into a tiny Koha::Reports::_processlist_rows helper so Test::MockModule can replace it cleanly. The helper is otherwise behaviour-preserving. Test plan: 1) prove t/db_dependent/Koha/Reports.t -> 11 tests, all pass. 2) Existing prep_report subtest still passes. Signed-off-by: Lucas Gass --- Koha/Reports.pm | 8 +++-- t/db_dependent/Koha/Reports.t | 65 ++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 3 deletions(-) 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;