From 94c81145a55cc5c04abbcf3d701ab2b9083e27ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Cohen=20Arazi?= Date: Sat, 20 Sep 2025 11:22:54 -0300 Subject: [PATCH] Bug 33782: Add unit tests for Koha::Auth::Client::OAuth This patch adds comprehensive unit tests for the OAuth authentication client, specifically covering the _get_data_and_patron method which handles JWT token processing and UserInfo endpoint integration. Test plan: 1. Apply patch 2. Run: $ ktd --shell k$ prove t/db_dependent/Koha/Auth/Client/OAuth.t => SUCCESS: Tests pass! 3. Tests cover: - JWT id_token processing and patron matching - UserInfo endpoint calls and data merging - Non-existent user scenarios - Combined JWT + UserInfo data sources 4. Sign off :-D --- t/db_dependent/Koha/Auth/Client/OAuth.t | 257 ++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100755 t/db_dependent/Koha/Auth/Client/OAuth.t diff --git a/t/db_dependent/Koha/Auth/Client/OAuth.t b/t/db_dependent/Koha/Auth/Client/OAuth.t new file mode 100755 index 0000000000..0da9a83fe7 --- /dev/null +++ b/t/db_dependent/Koha/Auth/Client/OAuth.t @@ -0,0 +1,257 @@ +#!/usr/bin/perl + +# Copyright 2025 Koha Development Team +# +# 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 . + +use Modern::Perl; + +use Test::More tests => 4; +use Test::MockModule; +use Test::NoWarnings; + +use JSON qw(encode_json); +use MIME::Base64 qw(encode_base64url); + +use Koha::Auth::Client::OAuth; +use Koha::Database; + +use t::lib::TestBuilder; + +my $schema = Koha::Database->new->schema; +my $builder = t::lib::TestBuilder->new; + +subtest '_get_data_and_patron() with id_token tests' => sub { + plan tests => 4; + + $schema->storage->txn_begin; + + my $client = Koha::Auth::Client::OAuth->new; + + # Create test patron + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { email => 'test@example.com' } + } + ); + + # Create provider with email matchpoint + my $provider = $builder->build_object( + { + class => 'Koha::Auth::Identity::Providers', + value => { + matchpoint => 'email', + mapping => encode_json( + { + email => 'mail', + firstname => 'given_name', + surname => 'family_name' + } + ) + } + } + ); + + # Create JWT token with user data + my $claims = { + mail => 'test@example.com', + given_name => 'John', + family_name => 'Doe' + }; + + my $id_token = 'header.' . encode_base64url( encode_json($claims) ) . '.signature'; + + my $data = { id_token => $id_token }; + my $config = {}; + + # Test the method + my ( $mapped_data, $found_patron ) = $client->_get_data_and_patron( + { + provider => $provider, + data => $data, + config => $config + } + ); + + # Verify results + is( $mapped_data->{email}, 'test@example.com', 'Email mapped correctly' ); + is( $mapped_data->{firstname}, 'John', 'First name mapped correctly' ); + is( $mapped_data->{surname}, 'Doe', 'Surname mapped correctly' ); + is( $found_patron->id, $patron->id, 'Patron found by email matchpoint' ); + + $schema->storage->txn_rollback; +}; + +subtest '_get_data_and_patron() with userinfo_url tests' => sub { + plan tests => 5; + + $schema->storage->txn_begin; + + my $client = Koha::Auth::Client::OAuth->new; + + # Create test patron + my $patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { email => 'userinfo@example.com' } + } + ); + + # Create provider with email matchpoint + my $provider = $builder->build_object( + { + class => 'Koha::Auth::Identity::Providers', + value => { + matchpoint => 'email', + mapping => encode_json( + { + email => 'email', + firstname => 'first_name' + } + ) + } + } + ); + + # Mock UserAgent for userinfo endpoint + my $ua_mock = Test::MockModule->new('Mojo::UserAgent'); + my $tx_mock = Test::MockModule->new('Mojo::Transaction::HTTP'); + my $res_mock = Test::MockModule->new('Mojo::Message::Response'); + my $headers_mock = Test::MockModule->new('Mojo::Headers'); + + $headers_mock->mock( 'content_type', sub { 'application/json' } ); + $res_mock->mock( 'code', sub { '200' } ); + $res_mock->mock( + 'json', + sub { + return { + email => 'userinfo@example.com', + first_name => 'Jane' + }; + } + ); + $res_mock->mock( + 'headers', + sub { + my $headers = {}; + bless $headers, 'Mojo::Headers'; + return $headers; + } + ); + + $tx_mock->mock( + 'res', + sub { + my $res = {}; + bless $res, 'Mojo::Message::Response'; + return $res; + } + ); + + $ua_mock->mock( + 'get', + sub { + my $tx = {}; + bless $tx, 'Mojo::Transaction::HTTP'; + return $tx; + } + ); + + my $data = { access_token => 'test_token' }; + my $config = { userinfo_url => 'https://provider.com/userinfo' }; + + # Test the method + my ( $mapped_data, $found_patron ) = $client->_get_data_and_patron( + { + provider => $provider, + data => $data, + config => $config + } + ); + + # Verify results + is( $mapped_data->{email}, 'userinfo@example.com', 'Email mapped from userinfo' ); + is( $mapped_data->{firstname}, 'Jane', 'First name mapped from userinfo' ); + is( $found_patron->id, $patron->id, 'Patron found by email from userinfo' ); + + # Test with both id_token and userinfo (patron should be found from id_token first) + my $id_token_claims = { email => 'token@example.com' }; + my $id_token = 'header.' . encode_base64url( encode_json($id_token_claims) ) . '.signature'; + + my $token_patron = $builder->build_object( + { + class => 'Koha::Patrons', + value => { email => 'token@example.com' } + } + ); + + $data->{id_token} = $id_token; + + ( $mapped_data, $found_patron ) = $client->_get_data_and_patron( + { + provider => $provider, + data => $data, + config => $config + } + ); + + is( $found_patron->id, $token_patron->id, 'Patron found from id_token takes precedence' ); + is( $mapped_data->{email}, 'userinfo@example.com', 'But userinfo data still merged' ); + + $schema->storage->txn_rollback; +}; + +subtest '_get_data_and_patron() no patron found tests' => sub { + plan tests => 2; + + $schema->storage->txn_begin; + + my $client = Koha::Auth::Client::OAuth->new; + + # Create provider + my $provider = $builder->build_object( + { + class => 'Koha::Auth::Identity::Providers', + value => { + matchpoint => 'email', + mapping => encode_json( { email => 'mail' } ) + } + } + ); + + # Create token for non-existent user + my $claims = { mail => 'nonexistent@example.com' }; + my $id_token = 'header.' . encode_base64url( encode_json($claims) ) . '.signature'; + + my $data = { id_token => $id_token }; + my $config = {}; + + # Test the method + my ( $mapped_data, $found_patron ) = $client->_get_data_and_patron( + { + provider => $provider, + data => $data, + config => $config + } + ); + + # Verify results + is( $mapped_data->{email}, 'nonexistent@example.com', 'Email mapped correctly' ); + is( $found_patron, undef, 'No patron found for non-existent email' ); + + $schema->storage->txn_rollback; +}; -- 2.51.0