Bugzilla – Attachment 193622 Details for
Bug 39224
Migrate SAML/Shibboleth configuration into Identity Providers
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 39224: Unit tests for identity provider object classes
374ffa8.patch (text/plain), 29.77 KB, created by
Martin Renvoize (ashimema)
on 2026-02-23 11:17:01 UTC
(
hide
)
Description:
Bug 39224: Unit tests for identity provider object classes
Filename:
MIME Type:
Creator:
Martin Renvoize (ashimema)
Created:
2026-02-23 11:17:01 UTC
Size:
29.77 KB
patch
obsolete
>From 374ffa8a071146ece2fb38a6dad1fcc9430c5a67 Mon Sep 17 00:00:00 2001 >From: Martin Renvoize <martin.renvoize@openfifth.co.uk> >Date: Sun, 22 Feb 2026 20:18:43 +0000 >Subject: [PATCH] Bug 39224: Unit tests for identity provider object classes > >Adds unit tests for the new Koha object classes: > >- t/db_dependent/Koha/Auth/Hostname.t: Tests for hostname CRUD > and validation >- t/db_dependent/Koha/Auth/Identity/Provider.t: Extended tests > for provider with mappings and hostnames >- t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t: Tests > for field mapping CRUD and validation >- t/db_dependent/Auth_with_shibboleth.t: Updated tests for new > provider structure > >Test plan: >1. Run: prove t/db_dependent/Koha/Auth/Hostname.t >2. Run: prove t/db_dependent/Koha/Auth/Identity/Provider.t >3. Run: prove t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >4. Run: prove t/db_dependent/Auth_with_shibboleth.t > >Sponsored-by: ByWater Solutions >--- > t/db_dependent/Auth_with_shibboleth.t | 83 ++++++- > t/db_dependent/Koha/Auth/Hostname.t | 139 ++++++++++++ > t/db_dependent/Koha/Auth/Identity/Provider.t | 158 +++++++++---- > .../Koha/Auth/Identity/Provider/Mapping.t | 207 ++++++++++++++++++ > 4 files changed, 537 insertions(+), 50 deletions(-) > create mode 100755 t/db_dependent/Koha/Auth/Hostname.t > create mode 100755 t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t > >diff --git a/t/db_dependent/Auth_with_shibboleth.t b/t/db_dependent/Auth_with_shibboleth.t >index 8a101ac07e6..ffa336b9991 100755 >--- a/t/db_dependent/Auth_with_shibboleth.t >+++ b/t/db_dependent/Auth_with_shibboleth.t >@@ -35,6 +35,13 @@ use t::lib::Dates; > use C4::Auth_with_shibboleth qw( shib_ok login_shib_url get_login_shib checkpw_shib ); > use Koha::Database; > use Koha::DateUtils qw( dt_from_string ); >+use Koha::Auth::Hostname; >+use Koha::Auth::Hostnames; >+use Koha::Auth::Identity::Provider; >+use Koha::Auth::Identity::Provider::Hostname; >+use Koha::Auth::Identity::Provider::Mapping; >+use Koha::Auth::Identity::Provider::SAML2; >+use Koha::Auth::Identity::Providers; > > BEGIN { > use_ok( >@@ -48,9 +55,7 @@ $schema->storage->txn_begin; > my $builder = t::lib::TestBuilder->new; > my $logger = t::lib::Mocks::Logger->new(); > >-# Mock variables > my $shibboleth; >-change_config( {} ); > my $interface = 'opac'; > > # Mock few preferences >@@ -59,12 +64,13 @@ t::lib::Mocks::mock_preference( 'StaffClientBaseURL', 'teststaff.com' ); > t::lib::Mocks::mock_preference( 'EmailFieldPrimary', '' ); > t::lib::Mocks::mock_preference( 'EmailFieldPrecedence', 'emailpro' ); > >-# Mock Context: config, tz and interface >+# Mock Context: tz and interface > my $context = Test::MockModule->new('C4::Context'); >-$context->mock( 'config', sub { return $shibboleth; } ); # easier than caching by Mocks::mock_config > $context->mock( 'timezone', sub { return 'local'; } ); > $context->mock( 'interface', sub { return $interface; } ); > >+change_config( {} ); >+ > # Mock Letters: GetPreparedLetter, EnqueueLetter and SendQueuedMessages > # We want to test the params > my $mocked_letters = Test::MockModule->new('C4::Letters'); >@@ -97,20 +103,22 @@ $mocked_letters->mock( > # Start testing ---------------------------------------------------------------- > > subtest "shib_ok tests" => sub { >- plan tests => 5; >+ plan tests => 6; > my $result; > > # correct config, no debug > is( shib_ok(), '1', "good config" ); > >- # bad config, no debug >+ # bad config, no debug - clear matchpoint in DB >+ $schema->resultset('IdentityProviderMapping')->update( { is_matchpoint => 0 } ); > delete $shibboleth->{matchpoint}; >+ $logger->clear(); > warnings_are { $result = shib_ok() } >- [ { carped => 'shibboleth matchpoint not defined' }, ], >- "undefined matchpoint = fatal config, warning given"; >+ [ { carped => 'shibboleth matchpoint not defined' } ], >+ "matchpoint warning given when no matchpoint set"; > is( $result, '0', "bad config" ); > >- change_config( { matchpoint => 'email' } ); >+ change_config( { matchpoint => 'email', mapping => { email => { content => 'default@example.com' } } } ); > warnings_are { $result = shib_ok() } > [ { carped => 'shibboleth matchpoint not mapped' }, ], > "unmapped matchpoint = fatal config, warning given"; >@@ -119,6 +127,7 @@ subtest "shib_ok tests" => sub { > # add test for undefined shibboleth block > $logger->clear; > change_config( {} ); >+ is( shib_ok(), '1', 'good config after reset' ); > }; > > subtest "login_shib_url tests" => sub { >@@ -279,6 +288,10 @@ subtest "checkpw_shib tests" => sub { > ); > > # sync user >+ my $saml2_provider = Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->next; >+ my $saml2_config = $saml2_provider->get_config // {}; >+ $saml2_config->{sync} = 1; >+ $saml2_provider->set_config($saml2_config)->store; > $shibboleth->{sync} = 1; > $ENV{'city'} = 'AnotherCity'; > ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login); >@@ -335,7 +348,7 @@ subtest "checkpw_shib tests" => sub { > $ENV{'emailpro'} = 'me@myemail.com'; > $ENV{branchcode} = $library->branchcode; # needed since T::D::C does no longer hides the FK constraint > >- checkpw($shib_login); >+ ( $retval, $retcard, $retuserid, $retpatron ) = checkpw_shib($shib_login); > ok( > my $new_user_autocreated = Koha::Patrons->find( { userid => 'test43210' } ), > "new user found" >@@ -424,6 +437,38 @@ $schema->storage->txn_rollback; > sub change_config { > my $params = shift; > >+ # Remove any existing SAML2 providers >+ Koha::Auth::Identity::Providers->search( { protocol => 'SAML2' } )->delete; >+ >+ my $provider = Koha::Auth::Identity::Provider::SAML2->new( >+ { >+ code => 'shibboleth', >+ description => 'Shibboleth', >+ enabled => 1, >+ } >+ )->store; >+ >+ $provider->set_config( >+ { >+ autocreate => $params->{autocreate} // 0, >+ sync => $params->{sync} // 0, >+ welcome => $params->{welcome} // 0, >+ } >+ )->store; >+ >+ # Link provider to the test hostname so _get_shib_config can find it >+ my $test_hostname = 'testopac.com'; >+ $ENV{SERVER_NAME} = $test_hostname; >+ my $hostname_obj = Koha::Auth::Hostnames->search( { hostname => $test_hostname } )->next >+ // $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => $test_hostname } } ); >+ Koha::Auth::Identity::Provider::Hostname->new( >+ { >+ hostname_id => $hostname_obj->hostname_id, >+ identity_provider_id => $provider->identity_provider_id, >+ is_enabled => 1, >+ } >+ )->store; >+ > my %mapping = ( > 'userid' => { 'is' => 'uid' }, > 'surname' => { 'is' => 'sn' }, >@@ -437,15 +482,29 @@ sub change_config { > if ( exists $params->{mapping} ) { > $mapping{$_} = $params->{mapping}->{$_} for keys %{ $params->{mapping} }; > } >+ >+ my $matchpoint = $params->{matchpoint} // 'userid'; >+ >+ foreach my $koha_field ( keys %mapping ) { >+ Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->identity_provider_id, >+ koha_field => $koha_field, >+ provider_field => $mapping{$koha_field}->{is}, >+ is_matchpoint => $koha_field eq $matchpoint ? 1 : 0, >+ default_content => $mapping{$koha_field}->{content}, >+ } >+ )->store; >+ } >+ > $shibboleth = { > autocreate => $params->{autocreate} // 0, > welcome => $params->{welcome} // 0, > sync => $params->{sync} // 0, >- matchpoint => $params->{matchpoint} // 'userid', >+ matchpoint => $matchpoint, > mapping => \%mapping, > }; > >- # Change environment too > $ENV{'uid'} = "test1234"; > $ENV{'sn'} = undef; > $ENV{'exp'} = undef; >diff --git a/t/db_dependent/Koha/Auth/Hostname.t b/t/db_dependent/Koha/Auth/Hostname.t >new file mode 100755 >index 00000000000..c66f993408b >--- /dev/null >+++ b/t/db_dependent/Koha/Auth/Hostname.t >@@ -0,0 +1,139 @@ >+#!/usr/bin/perl >+ >+# Copyright Koha Community 2026 >+# >+# This file is part of Koha >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 4; >+ >+use Koha::Auth::Hostname; >+use Koha::Auth::Hostnames; >+ >+use t::lib::TestBuilder; >+use t::lib::Mocks; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+subtest 'sync_from_syspref() tests' => sub { >+ >+ plan tests => 12; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::Auth::Hostnames->search->delete; >+ >+ # OPACBaseURL maps to hostname_id=1 >+ t::lib::Mocks::mock_preference( 'OPACBaseURL', 'https://opac.example.com' ); >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.com' ); >+ >+ my $opac = Koha::Auth::Hostnames->find(1); >+ ok( $opac, 'OPACBaseURL creates hostname_id=1' ); >+ is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has correct hostname' ); >+ >+ # staffClientBaseURL maps to hostname_id=2 >+ Koha::Auth::Hostname->sync_from_syspref( 'staffClientBaseURL', 'https://staff.example.com' ); >+ >+ my $staff = Koha::Auth::Hostnames->find(2); >+ ok( $staff, 'staffClientBaseURL creates hostname_id=2' ); >+ is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has correct hostname' ); >+ >+ # Updating the URL changes the stored hostname in-place >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'newopac.example.com', 'hostname_id=1 updated when OPACBaseURL changes' ); >+ is( Koha::Auth::Hostnames->search( { hostname_id => 1 } )->count, 1, 'Still only one row for hostname_id=1' ); >+ >+ # Idempotent: calling again with the same value is a no-op >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://newopac.example.com' ); >+ is( >+ Koha::Auth::Hostnames->search( { hostname => 'newopac.example.com' } )->count, 1, >+ 'Idempotent: no duplicate created' >+ ); >+ >+ # http:// URLs are supported >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'http://opac.example.org' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'opac.example.org', 'http:// URL extracted correctly' ); >+ >+ # Port numbers are stripped >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'https://opac.example.net:8080/path' ); >+ $opac->discard_changes; >+ is( $opac->hostname, 'opac.example.net', 'Port number stripped from hostname' ); >+ >+ # Empty value is a no-op >+ my $hostname_before = $opac->hostname; >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', '' ); >+ $opac->discard_changes; >+ is( $opac->hostname, $hostname_before, 'Empty value leaves existing hostname unchanged' ); >+ >+ # Malformed URL (no protocol) is a no-op >+ Koha::Auth::Hostname->sync_from_syspref( 'OPACBaseURL', 'not-a-valid-url' ); >+ $opac->discard_changes; >+ is( $opac->hostname, $hostname_before, 'Malformed URL leaves existing hostname unchanged' ); >+ >+ # Unknown preference name is ignored >+ Koha::Auth::Hostname->sync_from_syspref( 'SomeOtherPref', 'https://other.example.com' ); >+ is( >+ Koha::Auth::Hostnames->search( { hostname => 'other.example.com' } )->count, >+ 0, 'Unknown syspref name is ignored' >+ ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'sync_from_sysprefs() tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ Koha::Auth::Hostnames->search->delete; >+ >+ t::lib::Mocks::mock_preference( 'OPACBaseURL', 'https://opac.example.com' ); >+ t::lib::Mocks::mock_preference( 'staffClientBaseURL', 'https://staff.example.com' ); >+ >+ Koha::Auth::Hostname->sync_from_sysprefs; >+ >+ my $opac = Koha::Auth::Hostnames->find(1); >+ my $staff = Koha::Auth::Hostnames->find(2); >+ >+ ok( $opac, 'hostname_id=1 exists after sync_from_sysprefs' ); >+ is( $opac->hostname, 'opac.example.com', 'hostname_id=1 has OPAC hostname' ); >+ >+ ok( $staff, 'hostname_id=2 exists after sync_from_sysprefs' ); >+ is( $staff->hostname, 'staff.example.com', 'hostname_id=2 has staff hostname' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Koha::Auth::Hostnames type tests' => sub { >+ >+ plan tests => 2; >+ >+ $schema->storage->txn_begin; >+ >+ my $hostname = $builder->build_object( { class => 'Koha::Auth::Hostnames' } ); >+ is( ref($hostname), 'Koha::Auth::Hostname', 'build_object returns correct object type' ); >+ >+ my $hostnames = Koha::Auth::Hostnames->search( { hostname_id => $hostname->hostname_id } ); >+ is( ref($hostnames), 'Koha::Auth::Hostnames', 'search returns correct collection type' ); >+ >+ $schema->storage->txn_rollback; >+}; >diff --git a/t/db_dependent/Koha/Auth/Identity/Provider.t b/t/db_dependent/Koha/Auth/Identity/Provider.t >index 8fe84cb8982..04e1decb374 100755 >--- a/t/db_dependent/Koha/Auth/Identity/Provider.t >+++ b/t/db_dependent/Koha/Auth/Identity/Provider.t >@@ -20,13 +20,17 @@ > use Modern::Perl; > > use Test::NoWarnings; >-use Test::More tests => 7; >+use Test::More tests => 8; > > use Test::MockModule; > use Test::Exception; > > use JSON qw(encode_json); > >+use Koha::Auth::Hostnames; >+use Koha::Auth::Identity::Provider; >+use Koha::Auth::Identity::Provider::Hostnames; >+use Koha::Auth::Identity::Provider::Mappings; > use Koha::Auth::Identity::Providers; > > use t::lib::TestBuilder; >@@ -88,7 +92,6 @@ subtest 'set_config() tests' => sub { > > my $provider = > $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC' } } ); >- $provider = $provider->upgrade_class; > > my $config = { > key => 'key', >@@ -114,7 +117,6 @@ subtest 'set_config() tests' => sub { > > my $provider = > $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OAuth' } } ); >- $provider = $provider->upgrade_class; > > my $config = { > key => 'key', >@@ -135,15 +137,15 @@ subtest 'set_config() tests' => sub { > is_deeply( $provider->get_config, $config, 'Configuration stored correctly' ); > }; > >- subtest 'Unsupported protocol tests' => sub { >+ subtest 'Base class (unsupported protocol) tests' => sub { > > plan tests => 2; > >- my $provider = >- $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'CAS' } } ); >+ # Cannot build in DB with invalid protocol; instantiate base class directly >+ my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'OAuth' } ); > >- throws_ok { $provider->set_config() } >- 'Koha::Exception', 'Exception thrown on unsupported protocol'; >+ throws_ok { $provider->set_config( {} ) } >+ 'Koha::Exception', 'Exception thrown when calling set_config on base class'; > > like( "$@", qr/This method needs to be subclassed/, 'Message is correct' ); > }; >@@ -151,48 +153,66 @@ subtest 'set_config() tests' => sub { > $schema->storage->txn_rollback; > }; > >-subtest 'get_mapping() tests' => sub { >+subtest 'mappings() tests' => sub { > >- plan tests => 2; >+ plan tests => 3; > > $schema->storage->txn_begin; > >- my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers', value => { config => '{' } } ); >- >- throws_ok { $provider->get_mapping() } >- 'Koha::Exceptions::Object::BadValue', 'Expected exception thrown on bad JSON'; >- >- my $mapping = { some => 'value', and => 'another' }; >- $provider->mapping( encode_json($mapping) )->store; >- >- is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' ); >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $mappings = $provider->mappings; >+ >+ is( ref($mappings), 'Koha::Auth::Identity::Provider::Mappings', 'Type is correct' ); >+ is( $mappings->count, 0, 'No mappings defined' ); >+ >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { identity_provider_id => $provider->id, koha_field => 'userid', provider_field => 'uid' } >+ } >+ ); >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { identity_provider_id => $provider->id, koha_field => 'email', provider_field => 'mail' } >+ } >+ ); >+ >+ is( $provider->mappings->count, 2, 'The provider has 2 mappings defined' ); > > $schema->storage->txn_rollback; > }; > >-subtest 'set_mapping() tests' => sub { >+subtest 'hostnames() tests' => sub { > >- plan tests => 1; >+ plan tests => 3; > > $schema->storage->txn_begin; > >- my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ my $hostnames = $provider->hostnames; > >- my $mapping = { some => 'value', and => 'another' }; >- $provider->set_mapping($mapping)->store; >+ is( ref($hostnames), 'Koha::Auth::Identity::Provider::Hostnames', 'Type is correct' ); >+ is( $hostnames->count, 0, 'No hostnames defined' ); >+ >+ $builder->build_object( >+ { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } ); >+ $builder->build_object( >+ { class => 'Koha::Auth::Identity::Provider::Hostnames', value => { identity_provider_id => $provider->id } } ); > >- is_deeply( $provider->get_mapping, $mapping, 'Mapping correctly retrieved' ); >+ is( $provider->hostnames->count, 2, 'The provider has 2 hostnames defined' ); > > $schema->storage->txn_rollback; > }; > >-subtest 'upgrade_class() tests' => sub { >+subtest 'polymorphic_retrieval() tests' => sub { > >- plan tests => 5; >+ plan tests => 7; > > $schema->storage->txn_begin; > >- my $mapping = Koha::Auth::Identity::Provider::protocol_to_class_mapping; >+ my $providers = Koha::Auth::Identity::Providers->new; >+ my $mapping = $providers->_polymorphic_map; > my @protocols = keys %{$mapping}; > > foreach my $protocol (@protocols) { >@@ -204,20 +224,82 @@ subtest 'upgrade_class() tests' => sub { > } > ); > >- is( ref($provider), 'Koha::Auth::Identity::Provider', "Base class used for $protocol" ); >+ is( ref($provider), $mapping->{$protocol}, "build_object returns correct subclass for $protocol" ); > >- # upgrade >- $provider = $provider->upgrade_class; >- is( >- ref($provider), $mapping->{$protocol}, >- "Class upgraded to " . $mapping->{$protocol} . "for protocol $protocol" >- ); >+ my $found = Koha::Auth::Identity::Providers->find( $provider->id ); >+ is( ref($found), $mapping->{$protocol}, "Providers->find returns correct subclass for $protocol" ); > } > > my $provider = Koha::Auth::Identity::Provider->new( { protocol => 'Invalid' } ); >- throws_ok { $provider->upgrade } >- 'Koha::Exception', >- 'Exception throw on invalid protocol'; >+ throws_ok { $provider->set_config( {} ) } 'Koha::Exception', 'Exception thrown calling set_config on base class'; >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'find_forced_provider() tests' => sub { >+ >+ plan tests => 8; >+ >+ $schema->storage->txn_begin; >+ >+ # No hostname argument returns undef >+ is( Koha::Auth::Identity::Providers->find_forced_provider(undef), undef, 'Returns undef when hostname is undef' ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider(''), undef, >+ 'Returns undef when hostname is empty string' >+ ); >+ >+ my $provider = $builder->build_object( >+ { class => 'Koha::Auth::Identity::Providers', value => { protocol => 'OIDC', enabled => 1 } } ); >+ >+ my $hostname_record = >+ $builder->build_object( { class => 'Koha::Auth::Hostnames', value => { hostname => 'sso.example.com' } } ); >+ >+ # Hostname linked but force_sso=0 (default) >+ $builder->build_object( >+ { >+ class => 'Koha::Auth::Identity::Provider::Hostnames', >+ value => { >+ identity_provider_id => $provider->id, >+ hostname_id => $hostname_record->hostname_id, >+ is_enabled => 1, >+ force_sso => 0, >+ } >+ } >+ ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when hostname is linked but force_sso is 0' >+ ); >+ >+ # Update to force_sso=1 but is_enabled=0 on the hostname link >+ Koha::Auth::Identity::Provider::Hostnames->search( >+ { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } ) >+ ->update( { force_sso => 1, is_enabled => 0 } ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when hostname link has is_enabled=0' >+ ); >+ >+ # Enable the hostname link but disable the provider itself >+ Koha::Auth::Identity::Provider::Hostnames->search( >+ { identity_provider_id => $provider->id, hostname_id => $hostname_record->hostname_id } ) >+ ->update( { is_enabled => 1 } ); >+ $provider->update( { enabled => 0 } ); >+ is( >+ Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'), >+ undef, 'Returns undef when provider itself is disabled' >+ ); >+ >+ # Re-enable the provider - now all conditions met >+ $provider->update( { enabled => 1 } ); >+ my $forced = Koha::Auth::Identity::Providers->find_forced_provider('sso.example.com'); >+ ok( $forced, 'Returns provider when hostname linked with force_sso=1, is_enabled=1, and provider enabled=1' ); >+ is( >+ ref($forced), 'Koha::Auth::Identity::Provider::OIDC', >+ 'Returned object is the correct Identity Provider subclass' >+ ); >+ is( $forced->id, $provider->id, 'Returns the correct provider' ); > > $schema->storage->txn_rollback; > }; >diff --git a/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t b/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >new file mode 100755 >index 00000000000..7c3b7e6ccdc >--- /dev/null >+++ b/t/db_dependent/Koha/Auth/Identity/Provider/Mapping.t >@@ -0,0 +1,207 @@ >+#!/usr/bin/perl >+ >+# Copyright Koha Community 2026 >+# >+# This file is part of Koha >+# >+# Koha is free software; you can redistribute it and/or modify it >+# under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# Koha is distributed in the hope that it will be useful, but >+# WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with Koha; if not, see <https://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Test::NoWarnings; >+use Test::More tests => 4; >+use Test::Exception; >+ >+use Koha::Auth::Identity::Provider::Mapping; >+use Koha::Auth::Identity::Provider::Mappings; >+use Koha::Auth::Identity::Providers; >+ >+use t::lib::TestBuilder; >+ >+my $schema = Koha::Database->new->schema; >+my $builder = t::lib::TestBuilder->new; >+ >+subtest 'Mapping::store() tests' => sub { >+ >+ plan tests => 8; >+ >+ $schema->storage->txn_begin; >+ >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ >+ # Must throw if neither provider_field nor default_content is set >+ throws_ok { >+ Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ } >+ )->store; >+ } >+ 'Koha::Exceptions::MissingParameter', >+ 'Exception thrown when neither provider_field nor default_content is provided'; >+ >+ like( $@->parameter, qr/provider_field or default_content/, 'Exception parameter message is correct' ); >+ >+ # Succeeds with provider_field only >+ my $m1 = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ provider_field => 'uid', >+ } >+ )->store; >+ ok( $m1, 'store() succeeds when only provider_field is supplied' ); >+ >+ # Succeeds with default_content only >+ my $m2 = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'categorycode', >+ default_content => 'PT', >+ } >+ )->store; >+ ok( $m2, 'store() succeeds when only default_content is supplied' ); >+ >+ # Succeeds with both provider_field and default_content >+ my $m3 = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'branchcode', >+ provider_field => 'branch', >+ default_content => 'CPL', >+ } >+ )->store; >+ ok( $m3, 'store() succeeds when both provider_field and default_content are supplied' ); >+ >+ # Setting is_matchpoint=1 clears any existing matchpoint for the same provider >+ my $first_mp = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'email', >+ provider_field => 'mail', >+ is_matchpoint => 1, >+ } >+ )->store; >+ is( $first_mp->is_matchpoint, 1, 'First matchpoint stored correctly' ); >+ >+ my $second_mp = Koha::Auth::Identity::Provider::Mapping->new( >+ { >+ identity_provider_id => $provider->id, >+ koha_field => 'surname', >+ provider_field => 'sn', >+ is_matchpoint => 1, >+ } >+ )->store; >+ >+ $first_mp->discard_changes; >+ is( $first_mp->is_matchpoint, 0, 'Previous matchpoint cleared when new matchpoint is stored' ); >+ is( $second_mp->is_matchpoint, 1, 'New matchpoint is active' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Mappings::get_matchpoint() tests' => sub { >+ >+ plan tests => 3; >+ >+ $schema->storage->txn_begin; >+ >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ >+ is( $provider->mappings->get_matchpoint, undef, 'Returns undef when provider has no mappings' ); >+ >+ # Add a non-matchpoint mapping via builder (bypasses store() constraint) >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ provider_field => 'uid', >+ is_matchpoint => 0, >+ } >+ } >+ ); >+ is( $provider->mappings->get_matchpoint, undef, 'Returns undef when no mapping is flagged as matchpoint' ); >+ >+ # Add a matchpoint mapping >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'email', >+ provider_field => 'mail', >+ is_matchpoint => 1, >+ } >+ } >+ ); >+ my $matchpoint = $provider->mappings->get_matchpoint; >+ is( $matchpoint->koha_field, 'email', 'Returns the mapping flagged as matchpoint' ); >+ >+ $schema->storage->txn_rollback; >+}; >+ >+subtest 'Mappings::as_auth_mapping() tests' => sub { >+ >+ plan tests => 4; >+ >+ $schema->storage->txn_begin; >+ >+ my $provider = $builder->build_object( { class => 'Koha::Auth::Identity::Providers' } ); >+ >+ my ( $mapping, $matchpoint ) = $provider->mappings->as_auth_mapping; >+ is_deeply( $mapping, {}, 'Returns empty hashref when provider has no mappings' ); >+ is( $matchpoint, undef, 'Returns undef matchpoint when provider has no matchpoint' ); >+ >+ # Create mappings: one with provider_field (matchpoint), one with default_content only >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'userid', >+ provider_field => 'uid', >+ default_content => undef, >+ is_matchpoint => 1, >+ } >+ } >+ ); >+ $builder->build( >+ { >+ source => 'IdentityProviderMapping', >+ value => { >+ identity_provider_id => $provider->id, >+ koha_field => 'categorycode', >+ provider_field => undef, >+ default_content => 'PT', >+ is_matchpoint => 0, >+ } >+ } >+ ); >+ >+ ( $mapping, $matchpoint ) = $provider->mappings->as_auth_mapping; >+ is_deeply( >+ $mapping, >+ { >+ userid => { is => 'uid', content => undef }, >+ categorycode => { is => undef, content => 'PT' }, >+ }, >+ 'Returns correct mapping hashref with is/content keys for each koha_field' >+ ); >+ is( $matchpoint, 'userid', 'Returns the koha_field name of the matchpoint mapping' ); >+ >+ $schema->storage->txn_rollback; >+}; >-- >2.53.0 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 39224
:
189234
|
189235
|
189236
|
189237
|
189238
|
189239
|
189240
|
189241
|
189242
|
189243
|
189244
|
193618
|
193619
|
193620
|
193621
|
193622
|
193623
|
193624
|
193625
|
193626
|
193627
|
193681
|
193682
|
193683
|
193684
|
193685
|
193686
|
193687
|
193688
|
193689
|
193690
|
193691
|
193692