From ecfb065bf868fd3ecd1c8cb17407d93e46e3020a Mon Sep 17 00:00:00 2001
From: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Date: Wed, 13 Dec 2023 15:12:30 +0000
Subject: [PATCH] Bug 35536: Add Koha::Plugins->RemovePlugins class method

Test plan:
Run t/db_dependent/Koha/Plugins/Plugins.t

Signed-off-by: Marcel de Rooy <m.de.rooy@rijksmuseum.nl>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
---
 Koha/Plugins.pm                       | 42 ++++++++++++
 t/db_dependent/Koha/Plugins/Plugins.t | 93 ++++++++++++++++++++++++++-
 2 files changed, 132 insertions(+), 3 deletions(-)

diff --git a/Koha/Plugins.pm b/Koha/Plugins.pm
index 72ae308f3ff..3f2bed21965 100644
--- a/Koha/Plugins.pm
+++ b/Koha/Plugins.pm
@@ -32,6 +32,7 @@ use C4::Output;
 
 use Koha::Cache::Memory::Lite;
 use Koha::Exceptions::Plugin;
+use Koha::Plugins::Datas;
 use Koha::Plugins::Methods;
 
 use constant ENABLED_PLUGINS_CACHE_KEY => 'enabled_plugins';
@@ -306,6 +307,47 @@ sub InstallPlugins {
     return @plugins;
 }
 
+=head2 RemovePlugins
+
+    Koha::Plugins->RemovePlugins( {
+        [ plugin_class => MODULE_NAME, destructive => 1, disable => 1 ],
+    } );
+
+    This is primarily for unit testing. Take care when you pass the
+    destructive flag (know what you are doing)!
+
+    The method removes records from plugin_methods for one or all plugins.
+
+    If you pass the destructive flag, it will remove records too from
+    plugin_data for one or all plugins. Destructive overrules disable.
+
+    If you pass disable, it will disable one or all plugins (in plugin_data).
+
+    If you do not pass destructive or disable, this method does not touch
+    records in plugin_data. The cache key for enabled plugins will be cleared
+    only if you pass disabled or destructive.
+
+=cut
+
+sub RemovePlugins {
+    my ( $class, $params ) = @_;
+
+    my $cond = {
+        $params->{plugin_class}
+        ? ( plugin_class => $params->{plugin_class} )
+        : ()
+    };
+    Koha::Plugins::Methods->search($cond)->delete;
+    if ( $params->{destructive} ) {
+        Koha::Plugins::Datas->search($cond)->delete;
+        Koha::Cache::Memory::Lite->clear_from_cache( Koha::Plugins->ENABLED_PLUGINS_CACHE_KEY );
+    } elsif ( $params->{disable} ) {
+        $cond->{plugin_key} = '__ENABLED__';
+        Koha::Plugins::Datas->search($cond)->update( { plugin_value => 0 } );
+        Koha::Cache::Memory::Lite->clear_from_cache( Koha::Plugins->ENABLED_PLUGINS_CACHE_KEY );
+    }
+}
+
 1;
 __END__
 
diff --git a/t/db_dependent/Koha/Plugins/Plugins.t b/t/db_dependent/Koha/Plugins/Plugins.t
index 71497a30363..3037ed801e4 100755
--- a/t/db_dependent/Koha/Plugins/Plugins.t
+++ b/t/db_dependent/Koha/Plugins/Plugins.t
@@ -24,11 +24,13 @@ use File::Temp qw( tempdir tempfile );
 use FindBin qw($Bin);
 use Module::Load::Conditional qw(can_load);
 use Test::MockModule;
-use Test::More tests => 16;
+use Test::More tests => 17;
 use Test::Warn;
 
 use C4::Context;
+use Koha::Cache::Memory::Lite;
 use Koha::Database;
+use Koha::Plugins::Datas;
 use Koha::Plugins::Methods;
 
 use t::lib::Mocks;
@@ -323,7 +325,92 @@ subtest 'Koha::Plugin::Test' => sub {
     $schema->storage->txn_rollback;
 };
 
-$schema->storage->txn_begin;    # matching rollback at the very end
+subtest 'RemovePlugins' => sub {
+    plan tests => 3;
+
+    $schema->storage->txn_begin;
+    t::lib::Mocks::mock_config( 'enable_plugins', 1 );
+
+    our $class_basename = 'Koha::Plugin::TestMR::' . time;
+
+    sub reload_plugin {
+        my ( $i, $mocks ) = @_;
+        Koha::Plugins::Data->new(
+            { plugin_class => "$class_basename$i", plugin_key => '__ENABLED__', plugin_value => 1 } )->store;
+        Koha::Plugins::Method->new( { plugin_class => "$class_basename$i", plugin_method => "testmr$i" } )->store;
+
+        # no_auto => 1 here prevents loading of a not-existing module
+        $mocks->[$i] = Test::MockModule->new( "$class_basename$i", no_auto => 1 )->mock( new => 1 )
+            unless $mocks->[$i];
+    }
+
+    # We will (re)create new plugins (without modules)
+    # This requires mocking can_load from Module::Load::Conditional
+    my $mlc_mock     = Test::MockModule->new('Koha::Plugins')->mock( can_load => 1 );
+    my $plugin_mocks = [];
+    my @enabled_plugins;
+
+    subtest 'Destructive flag' => sub {
+        reload_plugin( $_, $plugin_mocks ) for 1 .. 3;
+        Koha::Plugins->RemovePlugins( { destructive => 1 } );
+        is( Koha::Plugins::Datas->count,   0, 'No data in plugin_data' );
+        is( Koha::Plugins::Methods->count, 0, 'No data in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;    # testing if cache cleared
+        is( scalar @enabled_plugins, 0, 'No enabled plugins' );
+
+        reload_plugin( $_, $plugin_mocks ) for 1 .. 3;
+        Koha::Plugins->RemovePlugins( { plugin_class => "${class_basename}2", destructive => 1 } );
+        is( Koha::Plugins::Datas->count,   2, '2 in plugin_data' );
+        is( Koha::Plugins::Methods->count, 2, '2 in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 2, '2 enabled plugins' );
+        Koha::Plugins->RemovePlugins( { destructive => 1 } );
+    };
+
+    subtest 'Disable flag' => sub {
+        reload_plugin( $_, $plugin_mocks ) for 1 .. 4;
+        Koha::Plugins->RemovePlugins( { disable => 1 } );
+        is( Koha::Plugins::Datas->count,   4, '4 in plugin_data' );
+        is( Koha::Plugins::Methods->count, 0, 'No data in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 0, '0 enabled plugins' );
+
+        reload_plugin( $_, $plugin_mocks ) for 5 .. 6;
+        Koha::Plugins->RemovePlugins( { plugin_class => "${class_basename}5", disable => 1 } );
+        is( Koha::Plugins::Datas->count,   6, '6 in plugin_data' );
+        is( Koha::Plugins::Methods->count, 1, '1 in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 1, '1 enabled plugins' );
+        Koha::Plugins->RemovePlugins( { destructive => 1 } );
+    };
+
+    subtest 'No flags' => sub {
+        reload_plugin( $_, $plugin_mocks ) for 1 .. 2;
+        Koha::Plugins->RemovePlugins;
+        is( Koha::Plugins::Datas->count,   2, '2 in plugin_data' );
+        is( Koha::Plugins::Methods->count, 0, 'No data in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 2, '2 enabled plugins' );
+
+        reload_plugin( $_, $plugin_mocks ) for 3 .. 4;
+        Koha::Plugins->RemovePlugins( { plugin_class => "${class_basename}4" } );
+        is( Koha::Plugins::Datas->count,   4, '4 in plugin_data' );
+        is( Koha::Plugins::Methods->count, 1, '1 in plugin_methods' );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 2, '2 enabled plugins (from cache)' );
+
+        # clear cache and try again, expect 4 since RemovePlugins did not touch plugin_data here
+        Koha::Cache::Memory::Lite->clear_from_cache( Koha::Plugins->ENABLED_PLUGINS_CACHE_KEY );
+        @enabled_plugins = Koha::Plugins->get_enabled_plugins;
+        is( scalar @enabled_plugins, 4, '4 enabled plugins' );
+        Koha::Plugins->RemovePlugins( { destructive => 1 } );
+    };
+
+    $schema->storage->txn_rollback;
+};
+
+
+$schema->storage->txn_begin;    # Matching rollback at very end
 
 subtest 'output and output_html tests' => sub {
 
@@ -412,5 +499,5 @@ subtest 'new() tests' => sub {
     is( ref($result), 'Koha::Plugins', 'calling new with enable_plugins makes it override the config' );
 };
 
-Koha::Plugins::Methods->delete;
 $schema->storage->txn_rollback;
+#!/usr/bin/perl
-- 
2.43.0