From 0b31cf40f3b19472eb77b7f275722497d1614fd8 Mon Sep 17 00:00:00 2001 From: Marcel de Rooy Date: Thu, 30 Jan 2020 13:51:57 +0000 Subject: [PATCH] Bug 24544: Introduce Koha::PID::Controller with unit test Content-Type: text/plain; charset=utf-8 This new module will facilitate inserting/updating PIDs in authority, biblio and item records. Test plan: Run t/db_dependent/Koha/PID/Controller.t Signed-off-by: Marcel de Rooy --- Koha/PID/Controller.pm | 401 +++++++++++++++++++++++++++++++++++ t/db_dependent/Koha/PID/Controller.t | 270 +++++++++++++++++++++++ 2 files changed, 671 insertions(+) create mode 100644 Koha/PID/Controller.pm create mode 100644 t/db_dependent/Koha/PID/Controller.t diff --git a/Koha/PID/Controller.pm b/Koha/PID/Controller.pm new file mode 100644 index 0000000000..b1ded3a205 --- /dev/null +++ b/Koha/PID/Controller.pm @@ -0,0 +1,401 @@ +package Koha::PID::Controller; + +# This file is part of Koha. +# +# Copyright (C) 2020 Rijksmuseum +# +# 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 . + +=head1 NAME + +Koha::PID::Controller -- Facilitate handling of persistent identifiers + +=cut + +use Modern::Perl; +#use Data::Dumper qw/Dumper/; + +use C4::AuthoritiesMarc qw//; +use C4::Biblio qw//; +use C4::Context; +use Koha::Authorities; +use Koha::Biblios; +use Koha::Exceptions; +use Koha::Items; +use Koha::Plugins; + +=head1 METHODS + +=head2 new + + my $ctrl = Koha::PID::Controller->new({ domain => _optional_domain_ }); + + Simple constructor; blesses any passed hash reference. + The object parses and caches the pref PID_Field during its lifetime. + It also keeps PID_Domain or the overriding parameter to extract PIDs in the get method. + +=cut + +sub new { + my ( $class, $params ) = @_; + my $self = ref($params) eq 'HASH' ? $params : {}; + + $self->{_field} = _parse_field(); + $self->{_domain} = $params->{domain} || C4::Context->preference('PID_Domain') || q/http/; + + return bless $self, $class; +} + +sub _parse_field { # Return hash for field/subfield per auth, biblio or item + my $pref = C4::Context->preference('PID_Field'); + # form is: auth:999$x,biblio:999$x,item (subfield $x optional) + + my $hash = {}; + foreach my $cat ( split /,/, $pref ) { + if( $cat eq 'item' ) { + $hash->{item} = 1; # items are handled differently + next; + } + next if $cat !~ /^(auth|biblio)[:=]/; + my @temp = split /[:=]/, $cat, 2; + next if @temp<2 || !$temp[1]; + if( $temp[1] =~ /(\d{3})\$(.)$/ ) { + $hash->{$temp[0]}->{tag} = $1; + $hash->{$temp[0]}->{subfield} = $2; + } elsif( $temp[1] =~ /(\d{3})/ ) { + $hash->{$temp[0]}->{tag} = $1; + $hash->{$temp[0]}->{subfield} = 'a' if $1 ge '010'; + } + } + return $hash; +} + +=head2 get + + my $pid = $ctrl->get({ + category => $category, + kohaIdentifier => $kohaIdentifier, + [ object => $object ], + }); + + Get persistent URI from record for category/kohaIdentifier. + Depends on PID_Field and PID_Domain (see new method). + Optionally pass corresponding Koha object/MARC record. + + Can be controlled with plugin method pid_get_hook. + Note: a plugin that returns false, makes get return undef. + + Returns URI or undef. + +=cut + +sub get { + my ( $self, $params ) = @_; + my $category = $params->{category}; + my $kohaIdentifier = $params->{kohaIdentifier}; + my $object = $params->{object}; + + return if !exists $self->{_field}->{$category}; + + my $record = $self->_get_record($category, $kohaIdentifier, $object); + return if !$self->_get_hook($category, $record); # optional plugin control + + my @fields; + if( $category eq 'item') { + # check domain and return the first == last inserted one + @fields = grep { /\Q$self->{_domain}\E/ } split ' | ', $record->uri//''; + return _first_uri(@fields); + } + + my $tag = $self->{_field}->{$category}->{tag}; + my $sf = $self->{_field}->{$category}->{subfield}; + if( !$sf ) { # control field, might be UNIMARC; no check on domain + return _first_uri( $record->field($tag)->data ); + } + + # Regular MARC21 walk-through for auth or biblio + @fields = map { $_->subfield($sf) && $_->subfield($sf) =~ /\Q$self->{_domain}\E/ ? $_->subfield($sf) : () } $record->field($tag); + return _first_uri(@fields); +} + +sub _get_record { + my ( $self, $category, $kohaIdentifier, $object ) = @_; + my $record; + if( $category eq 'biblio' ) { + $object = Koha::Biblios->find($kohaIdentifier) unless $object; + $record = $object->metadata->record; + } elsif( $category eq 'item' ) { + $record = $object // Koha::Items->find($kohaIdentifier); + } else { # assume authority + $object = Koha::Authorities->find( $kohaIdentifier ) unless $object; + $record = MARC::Record->new_from_xml($object->marcxml, 'UTF-8', C4::Context->preference("marcflavour") eq "UNIMARC" ? "UNIMARCAUTH" : "MARC21" ) + if $object; + } + Koha::Exceptions::ObjectNotFound->throw( error => "No record found for $category:$kohaIdentifier\n" ) if !$record; + return $record; +} + +sub _get_hook { # these plugins may modify $record to control results, or return false + my ($self, $category, $record) = @_; + my $rv; + foreach my $plugin ( Koha::Plugins->new->GetPlugins({ method => 'pid_get_hook' }) ) { + $rv = $plugin->pid_get_hook({ record => $record, category => $category }); + return if !$rv; + } + return 1; +} + +sub _first_uri { + my ($first, @rest) = @_; + return if !defined $first; + $first =~ s/^.*http/http/; # could be spaces or perhaps orgcode in 035, not expecting two http occurrences + return $first; +} + +=head2 create + + my $uri = $ctrl->create({ category => $category, kohaIdentifier => $kohaIdentifier }); + + Creates a new persistent identifier URI. + This is plugin controlled. It needs a pid_create_hook method. The first returned true value is used. + Preferably, this plugin should be the interface to an external service. + Category, kohaIdentifier and domain (from object creation) are passed to the plugin. + Note that authtypecode (like PERSO_NAME etc.) is passed as category for authority records. + Does not (directly) depend on PID_Field and PID_Domain. + + Returns { success =>0|1, [ uri => ... ], [ info => 'additional' ] } + +=cut + +sub create { + my ( $self, $params ) = @_; + my $category = $params->{category}; + my $kohaIdentifier = $params->{kohaIdentifier}; + + my $authtypecode; + if( $category eq 'auth' ) { + my $rec = Koha::Authorities->find( $kohaIdentifier ); + $authtypecode = $rec->authtypecode if $rec; + } + + # Call plugin + my @plugins = Koha::Plugins->new->GetPlugins({ method => 'pid_create_hook' }); + return { success => 0, info => 'NO_PLUGIN' } if !@plugins; + my $domain = $self->{_domain} eq 'http' ? q{} : $self->{_domain}; + my $rv; + foreach my $plugin ( @plugins ) { + $rv = eval { $plugins[0]->pid_create_hook({ category => $authtypecode // $category, identifier => $kohaIdentifier, domain => $domain }) }; + last if $rv; # first true value + } + if( !$rv ) { + return { success => 0, info => $@ // q{} }; + } else { + return { success => 1, uri => $rv }; + } +} + +=head2 insert + + $ctrl->insert({ + pid => $pid, + category => $category, + kohaIdentifier => $kohaIdentifier, + [ object => $object ], + [ do_not_save => 1 ], + }); + + Insert $pid into the record referenced by $category and $kohaIdentifier. + Optionally pass corresponding Koha object/MARC record. + Depends on PID_Field. + + Behavior can be controlled with plugin hook pid_insert_hook. + The plugin is called with $pid, record, $category and inserted flag. + The inserted flag tells if a new field was inserted (no exact match found). + + The do_not_save flag may be used for optimization. Use it with care. + Returns an errcode if the $pid already exists. + + Returns { success => 1|0, [record => $marc | $item ], [ errcode => X ] } + +=cut + +sub insert { + my ( $self, $params ) = @_; + my $pid = $params->{pid}; + my $category = $params->{category}; + my $kohaIdentifier = $params->{kohaIdentifier}; + my $object = $params->{object}; + + return { success => 0, errcode => 'INV_CAT' } if !exists $self->{_field}->{$category}; + my $record = $self->_get_record($category, $kohaIdentifier, $object); + + # Add new field + my $insert; + if( $category eq 'item') { + $insert = $self->_update_item_field( $record, $pid ); + } else { + $insert = $self->_update_marc_record( $record, $category, $pid ); + } + + # Call plugin + my (@plugins, $rv); + @plugins = Koha::Plugins->new->GetPlugins({ method => 'pid_insert_hook' }); + foreach my $plugin ( @plugins ) { + $rv = $plugin->pid_insert_hook({ pid => $pid, record => $record, category => $category, inserted => $insert }); + return { success => 0, errcode => 'HOOK' } if !$rv; + } + return { success => 0, errcode => 'EXIST' } if !@plugins && !$insert; + $self->_default_insert_hook({ pid => $pid, record => $record, category => $category }) if !@plugins; + + # Save + $category = Koha::Authorities->find( $kohaIdentifier )->authtypecode if $category eq 'auth'; # should exist + $self->_save_record( $category, $kohaIdentifier, $record ) unless $params->{do_not_save}; + return { success => 1, record => $record }; +} + +sub _update_item_field { + my ($self, $record, $pid ) = @_; + + my $value = $record->uri; + if( !$value ) { + $value = $pid; + } elsif( $value =~ /(^|\| )$pid($| \|)/ ) { # exists + return; + } else { # insert in front + $value= "$pid | $value"; + } + $record->uri( $value ); + return 1; +} + +sub _update_marc_record { + my ( $self, $record, $category, $pid ) = @_; + my $tag = $self->{_field}->{$category}->{tag}; + my $sf = $self->{_field}->{$category}->{subfield}; + + if(!$sf) { # control field + if( $record->field($tag) ) { + return if $record->field($tag)->data eq $pid; + $record->field($tag)->update( $pid ); # overwrite + } else { + $record->insert_fields_ordered( MARC::Field->new($tag, $pid) ); + } + } else { + # Adds a new field unless we find an exact match + return if grep { $_->subfield($sf) && $_->subfield($sf) eq $pid } $record->field($tag); + my $field = MARC::Field->new( $tag, '', '', $sf => $pid ); + $record->insert_fields_ordered($field); + } + return 1; +} + +sub _default_insert_hook { +# If there are no hooks, this sub is used to update 024 when you specified it in PID_Field +# Note: If you want to disable this behavior, add a hook returning true + my ($self, $params) = @_; # pid, record, category + my $category = $params->{category}; + my $record = $params->{record}; + if( $category eq 'item' ) { # skip + } elsif( $self->{_field}->{$category}->{tag} eq '024' ) { + $record->field('024')->update( ind1 => '7', 2 => 'uri' ); # affects only first inserted 024 + } +} + +sub _save_record { + my ( $self, $category, $kohaIdentifier, $record ) = @_; + + if( $category eq 'item' ) { + $record->store; # previously calling ModItem + } elsif( $category eq 'biblio' ) { + C4::Biblio::ModBiblio($record, $kohaIdentifier, C4::Biblio::GetFrameworkCode($kohaIdentifier)); + } else { + C4::AuthoritiesMarc::ModAuthority( $kohaIdentifier, $record, $category ); + } +} + +1; +__END__ + +=head1 SYNOPSIS + +use Koha::PID::Controller; +my $ctrl = Koha::PID::Controller->new; + +my $pid = $ctrl->get({ category => $category, kohaIdentifier => $kohaIdentifier }); + +my $newpid = $ctrl->create({ category => $category, kohaIdentifier => $kohaIdentifier }); +if( !$ctrl->insert({ pid => $newpid, category => $category, kohaIdentifier => $kohaIdentifier })->{success} ) { + # tell me +} + +=head1 DESCRIPTION + +This controller allows you to maintain persistent identifiers in MARC records +of bibliographic and authority records for MARC21 and UNIMARC, and in items. + +Operation is controlled by preferences PID_Field and PID_Domain. PID_Field allows +you to specify which categories (auth, biblio, item) have PIDs, and where to store +them. PID_Domain is used as a base URL for finding PIDs. + +The module has three main methods: get, create and insert. Get extracts a PID from +a record. Create generates a new PID. And insert saves a PID into a record. All three +methods have a plugin hook to control the operation: pid_get_hook, pid_create_hook and +pid_insert_hook. The get and insert method do not depend on a plugin hook, but the create +method does not provide results without pid_create_hook. + +Preference PID_Field allows you to store the PIDs for biblio and authority records +anywhere in the MARC record. But MARC21 field 024 or UNIMARC field 003 would +probably be your best choice. (If you choose 024, a default insert hook +updates indicator1 and adds $2 for you. Only in absence of other insert hooks.) + +=head1 ADDITIONAL COMMENTS + +=head2 pid_get_hook + +This hook receives a parameter hash with record and category. It should return true +or false. + +Before the get method looks at the record, it runs the get hooks. If one returns false, +get returns undef. Furthermore, the hook receives the MARC record or item object. +So it allows you to also control results by changing field values (which are not saved +in the method, but be careful when you also use the object parameter). + +Get performs a contains match on PID_Domain. If this is too simple to fit your needs, +your get hook could reorder or remove fields so that get returns what you want. Note +that this may involve choosing an intelligent value for PID_Domain. + +=head2 pid_create_hook + +This hook receives a parameter hash with category, identifier and domain. It should +return a URI or false. The first true value is used. + +The create hook is meant to be the interface to your PID generator. Which should normally +be independent of the source system (Koha). You could also use it as your PID generator, +although purists won't like that. + +=head2 pid_insert_hook + +This hook receives a parameter hash with pid, record, category and inserted. It should +return true or false. (Inserted flag: see below.) A false value makes insert return an +error condition while not saving the change. + +The hook is called after the insert method adds a new field. But it did not add a field +when an exact match was found. The inserted flag tells the plugin about that. The hook +allows you to customize field changes. + +=head1 AUTHOR + +Marcel de Rooy, Rijksmuseum + +=cut diff --git a/t/db_dependent/Koha/PID/Controller.t b/t/db_dependent/Koha/PID/Controller.t new file mode 100644 index 0000000000..ae1112ff8c --- /dev/null +++ b/t/db_dependent/Koha/PID/Controller.t @@ -0,0 +1,270 @@ +#!/usr/bin/perl + +# This file is part of Koha. +# +# Copyright (C) 2020 Rijksmuseum +# +# 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 Data::Dumper qw/Dumper/; +use MARC::Record; +use Test::More tests => 4; +use Test::MockModule; +use Test::MockObject; +use Test::Warn; + +use t::lib::Mocks; +use t::lib::TestBuilder; + +use C4::AuthoritiesMarc qw//; +use C4::Biblio qw//; +use C4::Context; +use C4::Items; +use Koha::Database; +use Koha::PID::Controller; + +our $schema = Koha::Database->new->schema; +our $builder = t::lib::TestBuilder->new; + +our @plugins; +my $plugin_module = Test::MockModule->new( 'Koha::Plugins' ); +$plugin_module->mock( 'GetPlugins', sub { return @plugins; }); + +$schema->storage->txn_begin; + +subtest 'Test get method (biblio)' => sub { + plan tests => 6; + + my $plugin = Test::MockObject->new; + + my $record = MARC::Record->new; + $record->append_fields( + MARC::Field->new( '003', 'http://unimarc.fr/U-003' ), + MARC::Field->new( '024', '', '', 1 => 'http://donotgohere.com/M24a', q => 'qual_a' ), + MARC::Field->new( '024', '', '', 1 => 'http://bestpersistentid.nl/M24b' ), + MARC::Field->new( '024', '', '', 1 => 'http://bestpersistentid.nl/M24c', q => 'qual_c' ), + MARC::Field->new( '035', '', '', a => '(RMA) http://mysite01.org/M35a' ), # deliberate spaces + MARC::Field->new( '035', '', '', a => '(RMAxx)http://mysite02.org/M35b' ), + MARC::Field->new( '035', '', '', a => '(RMAxx)http://bestpersistentid.nl/M35c' ), + MARC::Field->new( '035', '', '', a => '(RMAx) http://mysite03.org/M35d' ), + ); + my ($biblionumber) = C4::Biblio::AddBiblio( $record, q{} ); + + t::lib::Mocks::mock_preference('PID_Field', 'biblio:003'); + t::lib::Mocks::mock_preference('PID_Domain', 'http://bestpersistentid.nl'); + my $ctrl = Koha::PID::Controller->new; + # UNIMARC 003, no domain control + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*U-003$/, 'Test get for UNIMARC biblio' ); + + t::lib::Mocks::mock_preference('PID_Field', 'biblio:024$1'); + $ctrl = Koha::PID::Controller->new; + # MARC21 024 $1, domain controlled + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M24b$/, 'Test get b for MARC21 biblio (024)' ); + + $plugin->mock( 'pid_get_hook', \&get_hook_24 ); + push @plugins, $plugin; + t::lib::Mocks::mock_preference('PID_Domain', q{}); + $ctrl = Koha::PID::Controller->new; + # MARC21 024 $1, no domain check, plugin hook (qualifier) + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M24c$/, 'Test get c for MARC21 biblio (024)' ); + pop @plugins; + + t::lib::Mocks::mock_preference('PID_Field', 'biblio:035'); # NOTE: we omit subfield + t::lib::Mocks::mock_preference('PID_Domain', 'http://bestpersistentid.nl'); + $ctrl = Koha::PID::Controller->new; + # MARC21 035 $a, domain controlled + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35c$/, 'Test get c for MARC21 biblio (035)' ); + + $ctrl = Koha::PID::Controller->new({ domain => 'site' }); + # MARC21 035 $a, incomplete domain check + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35a$/, 'Test get a for MARC21 biblio (035)' ); + + $plugin->mock( 'pid_get_hook', \&get_hook_35 ); + push @plugins, $plugin; + t::lib::Mocks::mock_preference('MARCOrgCode', 'RMAx'); + t::lib::Mocks::mock_preference('PID_Domain', q{}); + $ctrl = Koha::PID::Controller->new; + # MARC21 035 $a, no domain check, plugin hook (orgcode) + like( $ctrl->get({ category => 'biblio', kohaIdentifier => $biblionumber }), qr/^http.*M35d$/, 'Test get d for MARC21 biblio (035)' ); + pop @plugins; +}; + +sub get_hook_24 { # Mimics plugin that makes get return 024 with condition on $q + my ($self, $params) = @_; + my $record = $params->{record}; + my $qual = 'qual_c'; + foreach my $fld ( $record->field('024') ) { + if( !$fld->subfield('q') || $fld->subfield('q') !~ /^\Q$qual\E/ ) { + $record->delete_fields( $fld ); + } + } + return 1; +} + +sub get_hook_35 { # Mimics plugin that makes get return 035 with orgcode + my ($self, $params) = @_; + my $record = $params->{record}; + my $orgcode = C4::Context->preference('MARCOrgCode'); + foreach my $fld ( $record->field('035') ) { + if( !$fld->subfield('a') || $fld->subfield('a') !~ /^\(\Q$orgcode\E\)/ ) { + $record->delete_fields( $fld ); + } + } + return 1; +} + +subtest 'Test get method (item)' => sub { + plan tests => 4; + + # Test item with object + t::lib::Mocks::mock_preference('PID_Field', 'item'); + t::lib::Mocks::mock_preference('PID_Domain', 'my'); # do not use this value normally + my $ctrl = Koha::PID::Controller->new; + my $item = $builder->build_object({ class => 'Koha::Items', value => { uri => 'uri01 | my02 | uri03 | http://site04.org' } }); + is( $ctrl->get({ category => 'item', object => $item }), 'my02', 'Test get my02 for item' ); + # Test fallback to http + t::lib::Mocks::mock_preference('PID_Domain', q{}); + $ctrl = Koha::PID::Controller->new; + like( $ctrl->get({ category => 'item', kohaIdentifier => $item->itemnumber }), qr/04\.org$/, 'Test get http item' ); + # Item hook + my $plugin = Test::MockObject->new; + $plugin->mock( 'pid_get_hook', \&get_hook_item ); + push @plugins, $plugin; + $ctrl->get({ category => 'item', object => $item }); + is( $item->itemnotes, 'Reached', 'Item hook executed' ); + pop @plugins; + # Test clearing PID_Field + t::lib::Mocks::mock_preference('PID_Field', q{}); + $ctrl = Koha::PID::Controller->new; + is( $ctrl->get({ category => 'item', kohaIdentifier => $item->itemnumber }), undef, 'Test no item category' ); +}; + +sub get_hook_item { # Simple hack to test execution + my ($self, $params) = @_; + my $record = $params->{record}; # Koha::Item + $record->itemnotes('Reached'); + return 1; +} + +subtest 'Test create' => sub { + plan tests => 4; + + my $plugin = Test::MockObject->new; + $plugin->mock( 'pid_create_hook', \&create_hook ); + push @plugins, $plugin; + my $ctrl = Koha::PID::Controller->new({ domain => 'https://id.rijksmuseum.nl' }); + is( $ctrl->create({ category => 'biblio', kohaIdentifier => 100000 })->{uri}, 'https://id.rijksmuseum.nl/biblio/100000', "Create biblio pid" ); + # Category is not checked + is( $ctrl->create({ category => 'unchecked', kohaIdentifier => 999 })->{uri}, 'https://id.rijksmuseum.nl/unchecked/999', "Create unchecked pid" ); + # Two plugins, 'whatever' comes first + unshift @plugins, Test::MockObject->new->mock( 'pid_create_hook', sub { 'whatever' } ); + my $rv = $ctrl->create({ category => 'auth', kohaIdentifier => 1234 }); + is( $rv->{uri}, 'whatever', "Result of first plugin returning true value" ); + # No plugins + @plugins = (); + is( $ctrl->create({ category => 'item', kohaIdentifier => 654321 })->{success}, 0, "Test without plugin" ); +}; + +sub create_hook { # Super trivial return + my ($self, $params) = @_; + return $params->{domain}. '/'. $params->{category}. '/'. $params->{identifier}; +} + +subtest 'Test insert' => sub { + plan tests => 19; + + t::lib::Mocks::mock_preference('PID_Field', 'biblio:024$1,item'); + my $ctrl = Koha::PID::Controller->new; + + # category undefined + is( $ctrl->insert({ pid => '1', category => 'auth', kohaIdentifier => 1 })->{errcode}, 'INV_CAT', 'Undefined category' ); + + # hook + my $plugin = Test::MockObject->new; + $plugin->mock( 'pid_insert_hook', \&insert_hook ); + push @plugins, $plugin; + my $item = $builder->build_object({ class => 'Koha::Items', value => { uri => 'A | B' } }); + $ctrl->insert({ pid => '1', category => 'item', kohaIdentifier => $item->itemnumber }); # 1 | A | B + is( @plugins, 0, 'Hook got executed' ); + # item + $item->discard_changes; + is( $item->uri, '1 | A | B', 'Check result of item insert' ); + # reinsert the same value + is( $ctrl->insert({ pid => '1', category => 'item', kohaIdentifier => $item->itemnumber })->{errcode}, 'EXIST', 'Check reinsert' ); + # Test do_not_save + my $result = $ctrl->insert({ pid => '2', category => 'item', kohaIdentifier => $item->itemnumber, do_not_save => 1 }); + like( $result->{record}->uri, qr/^2/, 'New uri in response' ); + $item->discard_changes; + is( $item->uri, '1 | A | B', 'But not saved as expected' ); + + # marc record changes (biblio or auth) + my $biblio = $builder->build_sample_biblio; + my $record; + $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber }); + $record = C4::Biblio::GetMarcBiblio({ biblionumber => $biblio->biblionumber }); + is( $record->subfield('024', '1'), '123', 'Check biblio pid 123' ); + is( $record->field('024')->indicator(1), '7', 'Check indicator1 from default hook' ); + is( $record->subfield('024', '2'), 'uri', 'Check $2 from default hook' ); + # reinsert (without hook) + $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber }); + is( $result->{errcode}, 'EXIST', 'Check reinsert 123' ); + # empty hook + $plugin->mock('after_biblio_action', sub {} ); # prevent warn + $plugin->mock('pid_insert_hook', sub { 1; } ); + push @plugins, $plugin; + $result = $ctrl->insert({ pid => '456', category => 'biblio', kohaIdentifier => $biblio->biblionumber }); + $record = C4::Biblio::GetMarcBiblio({ biblionumber => $biblio->biblionumber }); + is( $record->subfield('024', '1'), '456', 'Check biblio pid 456' ); + is( $record->field('024')->indicator(1), q{ }, 'Check if indicator1 blank' ); + is( $record->subfield('024', '2'), undef, 'Check if $2 empty' ); + # reinsert (with hook) + $result = $ctrl->insert({ pid => '123', category => 'biblio', kohaIdentifier => $biblio->biblionumber }); + is( $result->{success}, 1, 'Reinsert with hook returns success' ); # since the hook 'fixes everything' + my @fields = $result->{record}->field('024'); + is( @fields, 2, 'Still expecting to find two 024s' ); + # hook returns false + my $plugin2 = Test::MockObject->new; + $plugin2->mock('pid_insert_hook', sub {} ); + push @plugins, $plugin2; + $result = $ctrl->insert({ pid => '567', category => 'biblio', kohaIdentifier => $biblio->biblionumber }); + is( $result->{errcode}, 'HOOK', 'Hook returned false' ); + @plugins = (); + + # PERSO_NAME authority + t::lib::Mocks::mock_preference('PID_Field', 'auth:024'); + $ctrl = Koha::PID::Controller->new; + $record = MARC::Record->new; + my $authid = C4::AuthoritiesMarc::AddAuthority( $record, undef, 'PERSO_NAME' ); + $result = $ctrl->insert({ pid => '234', category => 'auth', kohaIdentifier => $authid }); + $record = $result->{record}; + is( $record->subfield('024', 'a'), '234', 'Check auth pid 234' ); + # control field + t::lib::Mocks::mock_preference('PID_Field', 'auth:003'); + $ctrl = Koha::PID::Controller->new; + $result = $ctrl->insert({ pid => '345', category => 'auth', kohaIdentifier => $authid }); + $record = $result->{record}; + is( $record->field('003')->data, '345', 'Check auth pid 345' ); + # reinsert + $result = $ctrl->insert({ pid => '345', category => 'auth', kohaIdentifier => $authid }); + is( $result->{errcode}, 'EXIST', 'Check reinsert 345' ); +}; + +sub insert_hook { + my ($self, $params) = @_; + pop @plugins; # self destructive ;) + return 1; +} + +$schema->storage->txn_rollback; -- 2.11.0