#!/usr/bin/perl # 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 utf8; use Test::More tests => 7; use Test::Exception; use t::lib::TestBuilder; use t::lib::Mocks; use Digest::MD5 qw( md5_base64 md5_hex ); use Try::Tiny; use C4::Context; use C4::Members; use C4::Members::Attributes qw( GetBorrowerAttributes ); use Koha::Patrons; use Koha::Patron::Attribute; BEGIN { use_ok('Koha::Patron::Modification'); use_ok('Koha::Patron::Modifications'); } my $schema = Koha::Database->new->schema; my $builder = t::lib::TestBuilder->new; subtest 'new() tests' => sub { plan tests => 3; $schema->storage->txn_begin; Koha::Patron::Modifications->search->delete; # Create new pending modification Koha::Patron::Modification->new( { verification_token => '1234567890', changed_fields => 'surname,firstname', surname => 'Hall', firstname => 'Kyle' } )->store(); ## Get the new pending modification my $borrower = Koha::Patron::Modifications->find( { verification_token => '1234567890' } ); ## Verify we get the same data is( $borrower->surname, 'Hall', 'Found modification has matching surname' ); throws_ok { Koha::Patron::Modification->new( { verification_token => '1234567890', changed_fields => 'surname,firstname', surname => 'Hall', firstname => 'Daria' } )->store(); } 'Koha::Exceptions::Patron::Modification::DuplicateVerificationToken', 'Attempting to add a duplicate verification raises the correct exception'; is( $@, 'Duplicate verification token 1234567890', 'Exception carries the right message' ); $schema->storage->txn_rollback; }; subtest 'store( extended_attributes ) tests' => sub { plan tests => 4; $schema->storage->txn_begin; Koha::Patron::Modifications->search->delete; my $patron = $builder->build( { source => 'Borrower' } )->{borrowernumber}; my $verification_token = md5_hex( time().{}.rand().{}.$$ ); my $valid_json_text = '[{"code":"CODE","value":"VALUE"}]'; my $invalid_json_text = '[{"code":"CODE";"value":"VALUE"}]'; Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,surname,extended_attributes', borrowernumber => $patron, surname => 'Hall', extended_attributes => $valid_json_text } )->store(); my $patron_modification = Koha::Patron::Modifications->search( { borrowernumber => $patron } ) ->next; is( $patron_modification->surname, 'Hall', 'Patron modification correctly stored with valid JSON data' ); is( $patron_modification->extended_attributes, $valid_json_text, 'Patron modification correctly stored with valid JSON data' ); $verification_token = md5_hex( time().{}.rand().{}.$$ ); throws_ok { Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,surname,extended_attributes', borrowernumber => $patron, surname => 'Hall', extended_attributes => $invalid_json_text } )->store(); } 'Koha::Exceptions::Patron::Modification::InvalidData', 'Trying to store invalid JSON in extended_attributes field raises exception'; is( $@, 'The passed extended_attributes is not valid JSON' ); $schema->storage->txn_rollback; }; subtest 'approve tests' => sub { plan tests => 20; $schema->storage->txn_begin; Koha::Patron::Modifications->search->delete; my $patron_hashref = $builder->build( { source => 'Borrower' } ); $builder->build( { source => 'BorrowerAttributeType', value => { code => 'CODE_1' } } ); $builder->build( { source => 'BorrowerAttributeType', value => { code => 'CODE_2', repeatable => 1 } } ); my $verification_token = md5_hex( time().{}.rand().{}.$$ ); my $valid_json_text = '[{"code":"CODE_1","value":"VALUE_1"},{"code":"CODE_2","value":0}]'; my $patron_modification = Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,firstname,extended_attributes', borrowernumber => $patron_hashref->{borrowernumber}, firstname => 'Kyle', extended_attributes => $valid_json_text } )->store(); ok( $patron_modification->approve, 'Patron modification correctly approved' ); my $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} ); isnt( $patron->firstname, $patron_hashref->{firstname}, 'Patron modification changed firstname' ); is( $patron->firstname, 'Kyle', 'Patron modification set the right firstname' ); my @patron_attributes = GetBorrowerAttributes( $patron->borrowernumber ); is( $patron_attributes[0][0]->{code}, 'CODE_1', 'Patron modification correctly saved attribute code' ); is( $patron_attributes[0][0]->{value}, 'VALUE_1', 'Patron modification correctly saved attribute value' ); is( $patron_attributes[0][1]->{code}, 'CODE_2', 'Patron modification correctly saved attribute code' ); is( $patron_attributes[0][1]->{value}, 0, 'Patron modification correctly saved attribute with value 0, not confused with delete' ); # Create a new Koha::Patron::Modification, skip extended_attributes to # bypass checks $patron_modification = Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,firstname', borrowernumber => $patron_hashref->{borrowernumber}, firstname => 'Kylie' } )->store(); # Add invalid JSON to extended attributes $patron_modification->extended_attributes( '[{"code":"CODE";"values:VALUES"}]'); throws_ok { $patron_modification->approve } 'Koha::Exceptions::Patron::Modification::InvalidData', 'The right exception is thrown if invalid data is on extended_attributes'; $patron = Koha::Patrons->find( $patron_hashref->{borrowernumber} ); isnt( $patron->firstname, 'Kylie', 'Patron modification didn\'t apply' ); # Try changing only a portion of the attributes my $bigger_json = '[{"code":"CODE_2","value":"Tomasito"},{"code":"CODE_2","value":"None"}]'; $verification_token = md5_hex( time() . {} . rand() . {} . $$ ); $patron_modification = Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,extended_attributes', borrowernumber => $patron->borrowernumber, extended_attributes => $bigger_json } )->store(); ok( $patron_modification->approve, 'Patron modification correctly approved' ); @patron_attributes = map { $_->unblessed } Koha::Patron::Attributes->search( { borrowernumber => $patron->borrowernumber } ); is( $patron_attributes[0]->{code}, 'CODE_1', 'Untouched attribute type is preserved (code)' ); is( $patron_attributes[0]->{attribute}, 'VALUE_1', 'Untouched attribute type is preserved (attribute)' ); is( $patron_attributes[1]->{code}, 'CODE_2', 'Attribute updated correctly (code)' ); is( $patron_attributes[1]->{attribute}, 'Tomasito', 'Attribute updated correctly (attribute)' ); is( $patron_attributes[2]->{code}, 'CODE_2', 'Attribute updated correctly (code)' ); is( $patron_attributes[2]->{attribute}, 'None', 'Attribute updated correctly (attribute)' ); my $empty_code_json = '[{"code":"CODE_2","value":""}]'; $verification_token = md5_hex( time() . {} . rand() . {} . $$ ); $patron_modification = Koha::Patron::Modification->new( { verification_token => $verification_token, changed_fields => 'borrowernumber,extended_attributes', borrowernumber => $patron->borrowernumber, extended_attributes => $empty_code_json } )->store(); ok( $patron_modification->approve, 'Patron modification correctly approved' ); @patron_attributes = map { $_->unblessed } Koha::Patron::Attributes->search( { borrowernumber => $patron->borrowernumber } ); is( $patron_attributes[0]->{code}, 'CODE_1', 'Untouched attribute type is preserved (code)' ); is( $patron_attributes[0]->{attribute}, 'VALUE_1', 'Untouched attribute type is preserved (attribute)' ); my $count = Koha::Patron::Attributes->search({ borrowernumber => $patron->borrowernumber, code => 'CODE_2' })->count; is( $count, 0, 'Attributes deleted when modification contained an empty one'); $schema->storage->txn_rollback; }; subtest 'pending_count() and pending() tests' => sub { plan tests => 16; $schema->storage->txn_begin; Koha::Patron::Modifications->search->delete; my $library_1 = $builder->build( { source => 'Branch' } )->{branchcode}; my $library_2 = $builder->build( { source => 'Branch' } )->{branchcode}; $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_1' } }); $builder->build({ source => 'BorrowerAttributeType', value => { code => 'CODE_2', repeatable => 1 } }); my $patron_1 = $builder->build( { source => 'Borrower', value => { branchcode => $library_1, flags => 1 } } ); my $patron_2 = $builder->build( { source => 'Borrower', value => { branchcode => $library_2 } } ); my $patron_3 = $builder->build( { source => 'Borrower', value => { branchcode => $library_2 } } ); $patron_1 = Koha::Patrons->find( $patron_1->{borrowernumber} ); $patron_2 = Koha::Patrons->find( $patron_2->{borrowernumber} ); $patron_3 = Koha::Patrons->find( $patron_3->{borrowernumber} ); my $verification_token_1 = md5_hex( time().{}.rand().{}.$$ ); my $verification_token_2 = md5_hex( time().{}.rand().{}.$$ ); my $verification_token_3 = md5_hex( time().{}.rand().{}.$$ ); Koha::Patron::Attribute->new({ borrowernumber => $patron_1->borrowernumber, code => 'CODE_1', attribute => 'hello' } )->store(); Koha::Patron::Attribute->new({ borrowernumber => $patron_2->borrowernumber, code => 'CODE_2', attribute => 'bye' } )->store(); my $modification_1 = Koha::Patron::Modification->new( { verification_token => $verification_token_1, changed_fields => 'borrowernumber,surname,firstname,extended_attributes', borrowernumber => $patron_1->borrowernumber, surname => 'Hall', firstname => 'Kyle', extended_attributes => '[{"code":"CODE_1","value":""}]' } )->store(); is( Koha::Patron::Modifications->pending_count, 1, 'pending_count() correctly returns 1' ); my $modification_2 = Koha::Patron::Modification->new( { verification_token => $verification_token_2, changed_fields => 'borrowernumber,surname,firstname,extended_attributes', borrowernumber => $patron_2->borrowernumber, surname => 'Smith', firstname => 'Sandy', extended_attributes => '[{"code":"CODE_2","value":"año"},{"code":"CODE_2","value":"ciao"}]' } )->store(); my $modification_3 = Koha::Patron::Modification->new( { verification_token => $verification_token_3, changed_fields => 'borrowernumber,surname,firstname', borrowernumber => $patron_3->borrowernumber, surname => 'Smithy', firstname => 'Sandy' } )->store(); is( Koha::Patron::Modifications->pending_count, 3, 'pending_count() correctly returns 3' ); my $pending = Koha::Patron::Modifications->pending(); is( scalar @{$pending}, 3, 'pending() returns an array with 3 elements' ); my @filtered_modifications = grep { $_->{borrowernumber} eq $patron_1->borrowernumber } @{$pending}; my $p1_pm = $filtered_modifications[0]; my $p1_pm_attribute_1 = $p1_pm->{extended_attributes}->[0]; is( scalar @{$p1_pm->{extended_attributes}}, 1, 'patron 1 has modification has one pending attribute modification' ); is( ref($p1_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' ); is( $p1_pm_attribute_1->attribute, '', 'patron 1 has an empty value for the attribute' ); @filtered_modifications = grep { $_->{borrowernumber} eq $patron_2->borrowernumber } @{$pending}; my $p2_pm = $filtered_modifications[0]; is( scalar @{$p2_pm->{extended_attributes}}, 2 , 'patron 2 has 2 attribute modifications' ); my $p2_pm_attribute_1 = $p2_pm->{extended_attributes}->[0]; my $p2_pm_attribute_2 = $p2_pm->{extended_attributes}->[1]; is( ref($p2_pm_attribute_1), 'Koha::Patron::Attribute', 'patron modification has single attribute object' ); is( ref($p2_pm_attribute_2), 'Koha::Patron::Attribute', 'patron modification has single attribute object' ); is( $p2_pm_attribute_1->attribute, 'año', 'patron modification has the right attribute change' ); is( $p2_pm_attribute_2->attribute, 'ciao', 'patron modification has the right attribute change' ); t::lib::Mocks::mock_userenv({ patron => $patron_1 }); is( Koha::Patron::Modifications->pending_count($library_1), 1, 'pending_count() correctly returns 1 if filtered by library' ); is( Koha::Patron::Modifications->pending_count($library_2), 2, 'pending_count() correctly returns 2 if filtered by library' ); $modification_1->approve; is( Koha::Patron::Modifications->pending_count, 2, 'pending_count() correctly returns 2' ); $modification_2->approve; is( Koha::Patron::Modifications->pending_count, 1, 'pending_count() correctly returns 1' ); $modification_3->approve; is( Koha::Patron::Modifications->pending_count, 0, 'pending_count() correctly returns 0' ); $schema->storage->txn_rollback; }; subtest 'dateofbirth tests' => sub { plan tests => 7; $schema->storage->txn_begin; # Cleaning the field my $patron = $builder->build_object( { class => 'Koha::Patrons', value => { dateofbirth => '1980-01-01', surname => 'a_surname' } } ); my $patron_modification = Koha::Patron::Modification->new( { changed_fields => 'borrowernumber,dateofbirth', borrowernumber => $patron->borrowernumber, dateofbirth => undef })->store; $patron_modification->approve; $patron->discard_changes; is( $patron->dateofbirth, undef, 'dateofbirth must a been set to NULL if required' ); # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here. Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete; # Adding a dateofbirth $patron_modification = Koha::Patron::Modification->new( { changed_fields => 'borrowernumber,dateofbirth', borrowernumber => $patron->borrowernumber, dateofbirth => '1980-02-02' })->store; $patron_modification->approve; $patron->discard_changes; is( $patron->dateofbirth, '1980-02-02', 'dateofbirth must a been set' ); is( $patron->surname, 'a_surname', 'surname must not be updated' ); # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here. Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete; # Modifying a dateofbirth $patron_modification = Koha::Patron::Modification->new( { changed_fields => 'borrowernumber,dateofbirth', borrowernumber => $patron->borrowernumber, dateofbirth => '1980-03-03', surname => undef })->store; $patron_modification->approve; $patron->discard_changes; is( $patron->dateofbirth, '1980-03-03', 'dateofbirth must a been updated' ); is( $patron->surname, 'a_surname', 'surname must not be updated' ); # FIXME ->approve must have been removed it, but it did not. There may be an hidden bug here. Koha::Patron::Modifications->search({ borrowernumber => $patron->borrowernumber })->delete; # Modifying something else $patron_modification = Koha::Patron::Modification->new( { changed_fields => 'borrowernumber,surname', borrowernumber => $patron->borrowernumber, surname => 'another_surname', dateofbirth => undef })->store; $patron_modification->approve; $patron->discard_changes; is( $patron->surname, 'another_surname', 'surname must be updated' ); is( $patron->dateofbirth, '1980-03-03', 'dateofbirth should not have been updated if not needed' ); $schema->storage->txn_rollback; };