From d22990b7b1c832f8234cb2f8b606d3bf6af84107 Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Wed, 25 Mar 2015 15:53:07 +0200 Subject: [PATCH] Bug 13906 - TestObjectFactory(ies) for Koha objects. Enable easy Test object creation/deletion from HASHes. Doing tests should be easy. One biggest hindrance in doing tests is the difficulty of doing so in Koha. We use a lot of hard-coded SQL, which occasionally requires quite a bit of fiddling to get it right. Satisfying complex object dependency chains is hard. For example, testing Overdue notice sending, one must create -letters -overuderules -circulationrules -borrowers -biblios -items -issues (checkouts) ... Also one must take care to clean up the database modifications afterward to make encapsulated tests which don't leak over other tests. This is especially so for front-end testing. TestObjectFactories significantly alleviate this pain. They try to recover from errors, like when the previous test crashed and left testing objects into the DB, preventing adding them again. Also they store what object they created to 3 different levels of stashes to facilitate complex test configurations, and they have the dignity to clean-up the test contexts after testing has happened (if the script doesn't crash)! UNIT TESTS included! USAGE: use t::db_dependent::TestObjects::Borrowers::BorrowerFactory; %#Setting up the test context my $testContext = {}; my $password = '1234'; my $borrowerFactory = t::db_dependent::TestObjects::Borrowers::BorrowerFactory->new(); my $borrowers = $borrowerFactory->createTestGroup([ {firstname => 'Olli-Antti', surname => 'Kivi', cardnumber => '1A01', branchcode => 'CPL', flags => '1', #superlibrarian, not exactly a very good way of doing permission testing? userid => 'mini_admin', password => $password, }, ], undef, $testContext); %#Test context set, starting testing: eval { ... #Run your tests here }; if ($@) { #Catch all leaking errors and gracefully terminate. warn $@; tearDown(); exit 1; } %#All tests done, tear down test context tearDown(); done_testing; sub tearDown { t::db_dependent::TestObjects::ObjectFactory->tearDownTestContext($testContext); } --- t/lib/TestObjects/BiblioFactory.pm | 115 ++++++++++++ t/lib/TestObjects/BorrowerFactory.pm | 182 ++++++++++++++++++ t/lib/TestObjects/CheckoutFactory.pm | 182 ++++++++++++++++++ t/lib/TestObjects/ItemFactory.pm | 115 ++++++++++++ t/lib/TestObjects/LetterTemplateFactory.pm | 103 +++++++++++ t/lib/TestObjects/ObjectFactory.pm | 186 +++++++++++++++++++ t/lib/TestObjects/objectFactories.t | 284 +++++++++++++++++++++++++++++ 7 files changed, 1167 insertions(+) create mode 100644 t/lib/TestObjects/BiblioFactory.pm create mode 100644 t/lib/TestObjects/BorrowerFactory.pm create mode 100644 t/lib/TestObjects/CheckoutFactory.pm create mode 100644 t/lib/TestObjects/ItemFactory.pm create mode 100644 t/lib/TestObjects/LetterTemplateFactory.pm create mode 100644 t/lib/TestObjects/ObjectFactory.pm create mode 100644 t/lib/TestObjects/objectFactories.t diff --git a/t/lib/TestObjects/BiblioFactory.pm b/t/lib/TestObjects/BiblioFactory.pm new file mode 100644 index 0000000..8ec4353 --- /dev/null +++ b/t/lib/TestObjects/BiblioFactory.pm @@ -0,0 +1,115 @@ +package t::lib::TestObjects::BiblioFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; + +use C4::Biblio; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub new { + my ($class) = @_; + + my $self = {}; + bless($self, $class); + return $self; +} + +=head t::lib::TestObjects::createTestGroup + + my $biblioFactory = t::lib::TestObjects::BiblioFactory->new(); + my $records = $biblioFactory->createTestGroup([ + {'biblio.title' => 'I wish I met your mother', + 'biblio.author' => 'Pertti Kurikka', + 'biblio.copyrightdate' => '1960', + 'biblioitems.isbn' => '9519671580', + 'biblioitems.itemtype' => 'BK', + }, + ], undef, $testContext1, $testContext2, $testContext3); + +Calls C4::Biblio::TransformKohaToMarc() to make a MARC::Record and add it to +the DB. Returns a HASH of MARC::Records. +The HASH is keyed with the biblionumber, or the given $hashKey. Using for example +'biblioitems.isbn' is very much recommended to make linking objects more easy. +The biblionumber is injected to the MARC::Record-object to be easily accessable, +so we can get it like this: + $records->{$key}->{biblionumber}; + +See C4::Biblio::TransformKohaToMarc() for how the biblio- or biblioitem-tables' +columns need to be given. + +See t::lib::TestObjects::ObjectFactory for more documentation +=cut + +sub createTestGroup { + my ($self, $biblios, $hashKey, $featureStash, $scenarioStash, $stepStash) = @_; + $self->_validateStashes($featureStash, $scenarioStash, $stepStash); + + my %records; + foreach my $biblio (@$biblios) { + my $record = C4::Biblio::TransformKohaToMarc($biblio); + my ($biblionumber, $biblioitemnumber) = C4::Biblio::AddBiblio($record,''); + $record->{biblionumber} = $biblionumber; + + my $key = $self->getHashKey($biblio, $biblionumber, $hashKey); + + $records{$key} = $record; + } + + $self->_persistToStashes(\%records, 'biblios', $featureStash, $scenarioStash, $stepStash); + + return \%records; +} + +=head + + my $records = createTestGroup(); + ##Do funky stuff + deleteTestGroup($records); + +Removes the given test group from the DB. + +=cut + +sub deleteTestGroup { + my ($self, $records) = @_; + + my ( $biblionumberFieldCode, $biblionumberSubfieldCode ) = + C4::Biblio::GetMarcFromKohaField( "biblio.biblionumber", '' ); + + my $schema = Koha::Database->new_schema(); + while( my ($key, $record) = each %$records) { + my $biblionumber = $record->subfield($biblionumberFieldCode, $biblionumberSubfieldCode); + $schema->resultset('Biblio')->search($biblionumber)->delete_all(); + $schema->resultset('Biblioitem')->search($biblionumber)->delete_all(); + } +} +sub _deleteTestGroupFromIdentifiers { + my ($self, $testGroupIdentifiers) = @_; + + my $schema = Koha::Database->new_schema(); + foreach my $isbn (@$testGroupIdentifiers) { + $schema->resultset('Biblio')->search({"biblioitems.isbn" => $isbn},{join => 'biblioitems'})->delete(); + $schema->resultset('Biblioitem')->search({isbn => $isbn})->delete(); + } +} + +1; diff --git a/t/lib/TestObjects/BorrowerFactory.pm b/t/lib/TestObjects/BorrowerFactory.pm new file mode 100644 index 0000000..2b41963 --- /dev/null +++ b/t/lib/TestObjects/BorrowerFactory.pm @@ -0,0 +1,182 @@ +package t::lib::TestObjects::BorrowerFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; +use Scalar::Util qw(blessed); + +use C4::Members; +use Koha::Borrowers; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub new { + my ($class) = @_; + + my $self = {}; + bless($self, $class); + return $self; +} + +=head createTestGroup( $data [, $hashKey, $testContexts...] ) +@OVERLOADED + + my $borrowerFactory = t::lib::TestObjects::BorrowerFactory->new(); + my $borrowers = $borrowerFactory->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '11A001', + branchcode => 'CPL', + }, + {firstname => 'Olli-Antti2', + surname => 'Kivi2', + cardnumber => '11A002', + branchcode => 'FPL', + }, + ], undef, $testContext1, $testContext2, $testContext3); + + #Do test stuff... + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext1); + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext2); + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext3); + +The HASH is keyed with the given $hashKey or 'koha.borrowers.cardnumber' +See C4::Members::AddMember() for how the table columns need to be given. + +@PARAM1 ARRAYRef of HASHRefs of C4::Members::AddMember()-parameters. +@PARAM2 koha.borrower-column which is used as the test context borrowers HASH key, + defaults to the most best option cardnumber. +@PARAM3-5 HASHRef of test contexts. You can save the given borrowers to multiple + test contexts. Usually one is enough. These test contexts are + used to help tear down DB changes. +@RETURNS HASHRef of $hashKey => $borrower-objects: + +See t::lib::TestObjects::ObjectFactory for more documentation +=cut + +sub createTestGroup { + my ($self, $objects, $hashKey, $featureStash, $scenarioStash, $stepStash) = @_; + $self->_validateStashes($featureStash, $scenarioStash, $stepStash); + $hashKey = 'cardnumber' unless $hashKey; + + my %objects; + foreach my $object (@$objects) { + + $self->validateAndPopulateDefaultValues($object, $hashKey); + + #Try to add the Borrower, but it might fail because of the barcode or other UNIQUE constraint. + #Catch the error and try looking for the Borrower if we suspect it is present in the DB. + my $borrowernumber; + eval { + $borrowernumber = C4::Members::AddMember(%$object); + }; + if ($@) { + if (blessed($@) && $@->isa('DBIx::Class::Exception') && + $@->{msg} =~ /Duplicate entry '.+?' for key 'cardnumber'/) { #DBIx should throw other types of exceptions instead of this general type :( + #This exception type is OK, we ignore this and try fetching the existing Object next. + warn "Recovering from duplicate exception.\n"; + } + else { + die $@; + } + } + + #If adding failed, we still get some strange borrowernumber result. + #Check for sure by finding the real borrower. + my $borrower = Koha::Borrowers->cast( $borrowernumber || $object ); + unless ($borrower) { + carp "BorrowerFactory:> No borrower for cardnumber '".$object->{cardnumber}."'"; + next(); + } + + my $key = $self->getHashKey($borrower, $borrowernumber, $hashKey); + + $objects{$key} = $borrower; + } + + $self->_persistToStashes(\%objects, 'borrowers', $featureStash, $scenarioStash, $stepStash); + + return \%objects; +} + +=head validateAndPopulateDefaultValues +@OVERLOAD + +Validates given Object parameters and makes sure that critical fields are given +and populates defaults for missing values. +=cut + +sub validateAndPopulateDefaultValues { + my ($self, $borrower, $hashKey) = @_; + $self->SUPER::validateAndPopulateDefaultValues($borrower, $hashKey); + + $borrower->{categorycode} = 'PT' unless $borrower->{categorycode}; + $borrower->{branchcode} = 'CPL' unless $borrower->{branchcode}; + $borrower->{dateofbirth} = '1985-10-12' unless $borrower->{dateofbirth}; +} + +=head deleteTestGroup +@OVERLOADED + + my $records = createTestGroup(); + ##Do funky stuff + deleteTestGroup($records); + +Removes the given test group from the DB. + +=cut + +sub deleteTestGroup { + my ($self, $objects) = @_; + + my $schema = Koha::Database->new_schema(); + while( my ($key, $object) = each %$objects) { + my $borrower = Koha::Borrowers->cast($object); + eval { + $borrower->delete(); + }; + if ($@) { + if (blessed($@) && $@->isa('DBIx::Class::Exception') && + #Trying to recover. Delete all Checkouts for the Borrower to be able to delete. + $@->{msg} =~ /a foreign key constraint fails.+?issues_ibfk_1/) { #DBIx should throw other types of exceptions instead of this general type :( + + my @checkouts = Koha::Checkouts->search({borrowernumber => $borrower->borrowernumber}); + foreach my $c (@checkouts) { $c->delete(); } + $borrower->delete(); + warn "Recovering from foreign key exception.\n"; + } + else { + die $@; + } + } + + } +} +sub _deleteTestGroupFromIdentifiers { + my ($self, $testGroupIdentifiers) = @_; + + my $schema = Koha::Database->new_schema(); + foreach my $key (@$testGroupIdentifiers) { + $schema->resultset('Borrower')->find({"cardnumber" => $key})->delete(); + } +} + +1; diff --git a/t/lib/TestObjects/CheckoutFactory.pm b/t/lib/TestObjects/CheckoutFactory.pm new file mode 100644 index 0000000..761d558 --- /dev/null +++ b/t/lib/TestObjects/CheckoutFactory.pm @@ -0,0 +1,182 @@ +package t::lib::TestObjects::CheckoutFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; +use DateTime; + +use C4::Circulation; +use C4::Members; +use C4::Items; +use Koha::Borrowers; +use Koha::Items; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub new { + my ($class) = @_; + + my $self = {}; + bless($self, $class); + return $self; +} + +=head t::lib::TestObjects::CheckoutFactory::createTestGroup( $data [, $hashKey, $checkoutBranchRule] ) + + my $checkoutFactory = t::lib::TestObjects::CheckoutFactory->new(); + my $checkouts = $checkoutFactory->createTestGroup([ + {#Checkout params + }, + {#More checkout params + }, + ], undef, $testContext1, $testContext2, $testContext3); + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext1); + +@PARAM1, ARRAY of HASHes. + [ { + cardnumber => '167Azava0001', + barcode => '167Nfafa0010', + daysOverdue => 7, #This Checkout's duedate was 7 days ago. If undef, then uses today as the checkout day. + daysAgoCheckedout => 28, #This Checkout hapened 28 days ago. If undef, then uses today. + }, + { + ... + } + ] +@PARAM2, String, the HASH-element to use as the returning HASHes key. +@PARAM3, String, the rule on where to check these Issues out: + 'homebranch', uses the Item's homebranch as the checkout branch + 'holdingbranch', uses the Item's holdingbranch as the checkout branch + undef, uses the current Environment branch + '', checks out all Issues from the given branchCode +@PARAM4-6 HASHRef of test contexts. You can save the given objects to multiple + test contexts. Usually one is enough. These test contexts are + used to help tear down DB changes. +@RETURNS HASHRef of $hashKey => $checkout-objects. + The HASH is keyed with -, or the given $hashKey. + Example: { + '11A001-167N0212' => { + cardnumber => , + barcode => , + ...other Checkout-object columns... + }, + ... +} +=cut + +sub createTestGroup { + my ($self, $objects, $hashKey, $checkoutBranchRule, $featureStash, $scenarioStash, $stepStash) = @_; + $self->_validateStashes($featureStash, $scenarioStash, $stepStash); + + #If running this test factory from unit tests or bare script, the context might not have been initialized. + unless (C4::Context->userenv()) { + C4::Context->_new_userenv('testGroupsTest'); + C4::Context->set_userenv(undef, undef, undef, + undef, undef, + 'CPL', undef, undef, + undef, undef, undef); + } + my $oldContextBranch = C4::Context->userenv()->{branch}; + + my %objects; + foreach my $checkoutParams (@$objects) { + my $borrower = C4::Members::GetMember(cardnumber => $checkoutParams->{cardnumber}); + my $item = Koha::Items->cast($checkoutParams->{barcode}); + + my $duedate = DateTime->now(time_zone => C4::Context->tz()); + if ($checkoutParams->{daysOverdue}) { + $duedate->subtract(days => $checkoutParams->{daysOverdue} ); + } + + my $checkoutdate = DateTime->now(time_zone => C4::Context->tz()); + if ($checkoutParams->{daysAgoCheckedout}) { + $checkoutdate->subtract(days => $checkoutParams->{daysAgoCheckedout} ); + } + + #Set the checkout branch + my $checkoutBranch; + if (not($checkoutBranchRule)) { + #Use the existing userenv()->{branch} + } + elsif ($checkoutBranchRule eq 'homebranch') { + $checkoutBranch = $item->homebranch; + } + elsif ($checkoutBranchRule eq 'holdingbranch') { + $checkoutBranch = $item->holdingbranch; + } + elsif ($checkoutBranchRule) { + $checkoutBranch = $checkoutBranchRule; + } + C4::Context->userenv()->{branch} = $checkoutBranch if $checkoutBranch; + + my $datedue = C4::Circulation::AddIssue( $borrower, $checkoutParams->{barcode}, $duedate, undef, $checkoutdate ); + #We want the issue_id as well. + my $checkout = Koha::Checkouts->find({ borrowernumber => $borrower->{borrowernumber}, itemnumber => $item->itemnumber }); + unless ($checkout) { + carp "CheckoutFactory:> No checkout for cardnumber '".$checkoutParams->{cardnumber}."' and barcode '".$checkoutParams->{barcode}."'"; + next(); + } + + my $key; + if ($hashKey) { + $key = $self->getHashKey($checkout, $checkout->issue_id, $hashKey); + } + else { + $key = $checkoutParams->{cardnumber}.'-'.$checkoutParams->{barcode}; + } + + $objects{$key} = $checkout; + } + + $self->_persistToStashes(\%objects, 'checkouts', $featureStash, $scenarioStash, $stepStash); + + C4::Context->userenv()->{branch} = $oldContextBranch; + return \%objects; +} + +=head + + my $objects = createTestGroup(); + ##Do funky stuff + deleteTestGroup($records); + +Removes the given test group from the DB. + +=cut + +sub deleteTestGroup { + my ($self, $objects) = @_; + + while( my ($key, $object) = each %$objects) { + my $checkout = Koha::Checkouts->cast($object); + $checkout->delete(); + } +} +sub _deleteTestGroupFromIdentifiers { + my ($self, $testGroupIdentifiers) = @_; + + my $schema = Koha::Database->new_schema(); + foreach my $key (@$testGroupIdentifiers) { + $schema->resultset('Issue')->find({"issue_id" => $key})->delete(); + } +} + +1; diff --git a/t/lib/TestObjects/ItemFactory.pm b/t/lib/TestObjects/ItemFactory.pm new file mode 100644 index 0000000..ac8e402 --- /dev/null +++ b/t/lib/TestObjects/ItemFactory.pm @@ -0,0 +1,115 @@ +package t::lib::TestObjects::ItemFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; + +use C4::Items; +use Koha::Items; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub new { + my ($class) = @_; + + my $self = {}; + bless($self, $class); + return $self; +} + +=head t::lib::TestObjects::ItemFactory::createTestGroup( $data [, $hashKey] ) +@OVERLOADED + +Returns a HASH of objects. +Each Item is expected to contain the biblionumber of the Biblio they are added into. + eg. $item->{biblionumber} = 550242; + +The HASH is keyed with the PRIMARY KEY, or the given $hashKey. + +See C4::Items::AddItem() for how the table columns need to be given. + +See t::lib::TestObjects::ObjectFactory for more documentation +=cut + +sub createTestGroup { + my ($self, $objects, $hashKey, $featureStash, $scenarioStash, $stepStash) = @_; + $self->_validateStashes($featureStash, $scenarioStash, $stepStash); + + my %objects; + foreach my $object (@$objects) { + my ($biblionumber, $biblioitemnumber, $itemnumber); + eval { + ($biblionumber, $biblioitemnumber, $itemnumber) = C4::Items::AddItem($object, $object->{biblionumber}); + }; + if ($@) { + if (blessed($@) && $@->isa('DBIx::Class::Exception') && + $@->{msg} =~ /Duplicate entry '.+?' for key 'itembarcodeidx'/) { #DBIx should throw other types of exceptions instead of this general type :( + #This exception type is OK, we ignore this and try fetching the existing Object next. + warn "Recovering from duplicate exception.\n"; + } + else { + die $@; + } + } + my $item = Koha::Items->cast($itemnumber || $object); + unless ($item) { + carp "ItemFactory:> No item for barcode '".$object->{barcode}."'"; + next(); + } + + my $key = $self->getHashKey($item, $itemnumber, $hashKey); + + $objects{$key} = $item; + } + + $self->_persistToStashes(\%objects, 'items', $featureStash, $scenarioStash, $stepStash); + + return \%objects; +} + +=head +@OVERLOADED + + my $objects = createTestGroup(); + ##Do funky stuff + deleteTestGroup($records); + +Removes the given test group from the DB. + +=cut + +sub deleteTestGroup { + my ($self, $objects) = @_; + + while( my ($key, $object) = each %$objects) { + my $item = Koha::Items->cast($object); + $item->delete(); + } +} +sub _deleteTestGroupFromIdentifiers { + my ($self, $testGroupIdentifiers) = @_; + + foreach my $key (@$testGroupIdentifiers) { + my $item = Koha::Items->cast($key); + $item->delete(); + } +} + +1; diff --git a/t/lib/TestObjects/LetterTemplateFactory.pm b/t/lib/TestObjects/LetterTemplateFactory.pm new file mode 100644 index 0000000..b99389e --- /dev/null +++ b/t/lib/TestObjects/LetterTemplateFactory.pm @@ -0,0 +1,103 @@ +package t::lib::TestObjects::LetterTemplateFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; + +use C4::Letters; +use Koha::LetterTemplates; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub new { + my ($class) = @_; + + my $self = {}; + bless($self, $class); + return $self; +} + +=head t::lib::TestObjects::LetterTemplateFactory->createTestGroup +Returns a HASH of Koha::LetterTemplate-objects +The HASH is keyed with the PRIMARY KEYS eg. 'circulation-ODUE2-CPL-print', or the given $hashKey. +=cut + +#Incredibly the Letters-module has absolutely no Create or Update-component to operate on Letter templates? +#Tests like these are brittttle. :( +sub createTestGroup { + my ($self, $objects, $hashKey, $featureStash, $scenarioStash, $stepStash) = @_; + $self->_validateStashes($featureStash, $scenarioStash, $stepStash); + + my %objects; + my $schema = Koha::Database->new()->schema(); + foreach my $object (@$objects) { + my $rs = $schema->resultset('Letter'); + my $result = $rs->update_or_create({ + module => $object->{module}, + code => $object->{code}, + branchcode => ($object->{branchcode}) ? $object->{branchcode} : '', + name => $object->{name}, + is_html => $object->{is_html}, + title => $object->{title}, + message_transport_type => $object->{message_transport_type}, + content => $object->{content}, + }); + + my @pks = $result->id(); + my $key = $self->getHashKey($object, join('-',@pks), $hashKey); + + $objects{$key} = Koha::LetterTemplates->cast($result); + } + + $self->_persistToStashes(\%objects, 'letterTemplates', $featureStash, $scenarioStash, $stepStash); + + return \%objects; +} + +=head + +Removes the given test group from the DB. + +=cut + +sub deleteTestGroup { + my ($self, $letterTemplates) = @_; + + my $schema = Koha::Database->new_schema(); + while( my ($key, $letterTemplate) = each %$letterTemplates ) { + $letterTemplate->delete(); + } +} + +sub _deleteTestGroupFromIdentifiers { + my $testGroupIdentifiers = shift; + + my $schema = Koha::Database->new_schema(); + foreach my $key (@$testGroupIdentifiers) { + my ($module, $code, $branchcode, $mtt) = split('-',$key); + $schema->resultset('Letter')->find({module => $module, + code => $code, + branchcode => $branchcode, + message_transport_type => $mtt, + })->delete(); + } +} + +1; diff --git a/t/lib/TestObjects/ObjectFactory.pm b/t/lib/TestObjects/ObjectFactory.pm new file mode 100644 index 0000000..1650acf --- /dev/null +++ b/t/lib/TestObjects/ObjectFactory.pm @@ -0,0 +1,186 @@ +package t::lib::TestObjects::ObjectFactory; + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; +use Carp; + +use Koha::Exception::BadParameter; + +=head createTestGroup( $data [, $hashKey, $testContexts...] ) +@ABSTRACT, OVERLOAD THIS FROM SUBCLASS + + my $factory = t::lib::TestObjects::ObjectFactory->new(); + my $objects = $factory->createTestGroup([ + #Imagine if using the BorrowerFactory + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '11A001', + branchcode => 'CPL', + ... + }, + #Or if using the ItemFactory + {biblionumber => 123413, + barcode => '11N002', + homebranch => 'FPL', + ... + }, + ], $hashKey, $testContext1, $testContext2, $testContext3); + + #Do test stuff... + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext1); + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext2); + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext3); + +The HASH is keyed with the given $hashKey or 'koha..cardnumber' +See the object constructor defined in the implementing Factory-class for how +the table columns need to be given. + +@PARAM1 ARRAYRef of HASHRefs of desired Object constructor parameters +@PARAM2 koha.-column which is used as the test context HASH key to find individual Objects, + defaults to to one of the UNIQUE database keys. +@PARAM3-5 HASHRef of test contexts. You can save the given Objects to multiple + test contexts. Usually one is enough. These test contexts are + used to help tear down DB changes. +@RETURNS HASHRef of $hashKey => Objects, eg. + { + borrowers => { $hashKey => {#borrower1 HASH}, + $hashKey => {#borrower2 HASH}, + } + } +=cut + +sub createTestGroup {} #OVERLOAD THIS FROM SUBCLASS +sub deleteTestGroup {} #OVERLOAD THIS FROM SUBCLASS + +=head tearDownTestContext + +Given a testContext stash populated using one of the TestObjectFactory implementations createTestGroup()-subroutines, +Removes all the persisted objects in the stash. + +TestObjectFactories must be lazy loaded here to make it possible for them to subclass this. +=cut + +sub tearDownTestContext { + my ($self, $stash) = @_; + + ##You should introduce tearDowns in such an order that to not provoke FOREIGN KEY issues. + if ($stash->{checkouts}) { + require t::lib::TestObjects::CheckoutFactory; + t::lib::TestObjects::CheckoutFactory->deleteTestGroup($stash->{checkouts}); + delete $stash->{checkouts}; + } + if ($stash->{items}) { + require t::lib::TestObjects::ItemFactory; + t::lib::TestObjects::ItemFactory->deleteTestGroup($stash->{items}); + delete $stash->{items}; + } + if ($stash->{biblios}) { + require t::lib::TestObjects::BiblioFactory; + t::lib::TestObjects::BiblioFactory->deleteTestGroup($stash->{biblios}); + delete $stash->{biblios}; + } + if ($stash->{borrowers}) { + require t::lib::TestObjects::BorrowerFactory; + t::lib::TestObjects::BorrowerFactory->deleteTestGroup($stash->{borrowers}); + delete $stash->{borrowers}; + } + if ($stash->{letterTemplates}) { + require t::lib::TestObjects::LetterTemplateFactory; + t::lib::TestObjects::LetterTemplateFactory->deleteTestGroup($stash->{letterTemplates}); + delete $stash->{letterTemplates}; + } +} + +sub getHashKey { + my ($self, $object, $primaryKey, $hashKey) = @_; + + if (ref($object) eq 'HASH') { + if ($hashKey && not($object->{$hashKey})) { + carp ref($self)."->getHashKey($object, $primaryKey, $hashKey):> Given ".ref($object)." has no \$hashKey '$hashKey'."; + } + return ($hashKey) ? $object->{$hashKey} : $primaryKey; + } + else { + if ($hashKey && not($object->$hashKey())) { + carp ref($self)."->getHashKey($object, $primaryKey, $hashKey):> Given ".ref($object)." has no \$hashKey '$hashKey'."; + } + return ($hashKey) ? $object->$hashKey() : $primaryKey; + } +} + +=head validateAndPopulateDefaultValues +@INTERFACE + +Validates given Object parameters and makes sure that critical fields are given +and populates defaults for missing values. +You must overload this in the subclassing factory if you want to validate and check the given parameters +=cut + +sub validateAndPopulateDefaultValues { + my ($self, $object, $hashKey) = @_; + + unless ($object->{$hashKey}) { + Koha::Exception::BadParameter->throw(error => ref($self)."():> You want to access test Objects using hashKey '$hashKey', but you haven't supplied it as a Object parameter. ObjectFactories need a unique identifier to function properly."); + } +} + +=head _validateStashes + + _validateStashes($featureStash, $scenarioStash, $stepStash); + +Validates that the given stahses are what they are supposed to be... , HASHrefs. +@THROWS Koha::Exception::BadParameter, if validation failed. +=cut + +sub _validateStashes { + my ($self, $featureStash, $scenarioStash, $stepStash) = @_; + + if ($featureStash && not(ref($featureStash) eq 'HASH')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->_validateStashes():> Stash '\$featureStash' is not a HASHRef! Leave it 'undef' if you don't want to use it."); + } + if ($scenarioStash && not(ref($scenarioStash) eq 'HASH')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->_validateStashes():> Stash '\$scenarioStash' is not a HASHRef! Leave it 'undef' if you don't want to use it."); + } + if ($stepStash && not(ref($stepStash) eq 'HASH')) { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->_validateStashes():> Stash '\$stepStash' is not a HASHRef! Leave it 'undef' if you don't want to use it."); + } +} + +=head _persistToStashes + + _persistToStashes($objects, $stashKey, $featureStash, $scenarioStash, $stepStash); + +Saves the given HASH to the given stashes using the given stash key. +=cut + +sub _persistToStashes { + my ($self, $objects, $stashKey, $featureStash, $scenarioStash, $stepStash) = @_; + + if ($featureStash || $scenarioStash || $stepStash) { + while( my ($key, $borrower) = each %$objects) { + $featureStash->{$stashKey}->{ $key } = $borrower if $featureStash; + $scenarioStash->{$stashKey}->{ $key } = $borrower if $scenarioStash; + $stepStash->{$stashKey}->{ $key } = $borrower if $stepStash; + } + } +} + +1; \ No newline at end of file diff --git a/t/lib/TestObjects/objectFactories.t b/t/lib/TestObjects/objectFactories.t new file mode 100644 index 0000000..b6c6885 --- /dev/null +++ b/t/lib/TestObjects/objectFactories.t @@ -0,0 +1,284 @@ +#!/usr/bin/perl + +# Copyright Vaara-kirjastot 2015 +# +# 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, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# + +use Modern::Perl; + +use Test::More; +use DateTime; + +use Koha::DateUtils; + +use t::lib::TestObjects::ObjectFactory; +use t::lib::TestObjects::BorrowerFactory; +use Koha::Borrowers; +use t::lib::TestObjects::ItemFactory; +use Koha::Items; +use t::lib::TestObjects::BiblioFactory; +use Koha::Biblios; +use t::lib::TestObjects::CheckoutFactory; +use Koha::Checkouts; +use t::lib::TestObjects::LetterTemplateFactory; +use Koha::LetterTemplates; + + +my $testContext = {}; #Gather all created Objects here so we can finally remove them all. + + + +########## BorrowerFactory subtests ########## +subtest 't::lib::TestObjects::BorrowerFactory' => sub { + my $subtestContext = {}; + ##Create and Delete. Add one + my $f = t::lib::TestObjects::BorrowerFactory->new(); + my $objects = $f->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '11A001', + branchcode => 'CPL', + }, + ], undef, $subtestContext, undef, $testContext); + is($objects->{'11A001'}->cardnumber, '11A001', "Borrower '11A001'."); + ##Add one more to test incrementing the subtestContext. + $objects = $f->createTestGroup([ + {firstname => 'Olli-Antti2', + surname => 'Kivi2', + cardnumber => '11A002', + branchcode => 'FFL', + }, + ], undef, $subtestContext, undef, $testContext); + is($subtestContext->{borrowers}->{'11A001'}->cardnumber, '11A001', "Borrower '11A001' from \$subtestContext."); #From subtestContext + is($objects->{'11A002'}->branchcode, 'FFL', "Borrower '11A002'."); #from just created hash. + + ##Delete objects + t::lib::TestObjects::ObjectFactory->tearDownTestContext($subtestContext); + my $object11A001 = Koha::Borrowers->find({cardnumber => '11A001'}); + ok (not($object11A001), "Borrower '11A001' deleted"); + my $object11A002 = Koha::Borrowers->find({cardnumber => '11A002'}); + ok (not($object11A002), "Borrower '11A002' deleted"); + + #Prepare for autoremoval. + $objects = $f->createTestGroup([ + {firstname => 'Olli-Antti', + surname => 'Kivi', + cardnumber => '11A001', + branchcode => 'CPL', + }, + {firstname => 'Olli-Antti2', + surname => 'Kivi2', + cardnumber => '11A002', + branchcode => 'FFL', + }, + ], undef, undef, undef, $testContext); +}; + + + +########## BiblioFactory and ItemFactory subtests ########## +subtest 't::lib::TestObjects::BiblioFactory and ::ItemFactory' => sub { + my $subtestContext = {}; + ##Create and Delete. Add one + my $fb = t::lib::TestObjects::BiblioFactory->new(); + my $biblios = $fb->createTestGroup([ + {'biblio.title' => 'I wish I met your mother', + 'biblio.author' => 'Pertti Kurikka', + 'biblio.copyrightdate' => '1960', + 'biblioitems.isbn' => '9519671580', + 'biblioitems.itemtype' => 'BK', + }, + ], 'biblioitems.isbn', $subtestContext, undef, $testContext); + my $f = t::lib::TestObjects::ItemFactory->new(); + my $objects = $f->createTestGroup([ + {biblionumber => $biblios->{9519671580}->{biblionumber}, + barcode => '167Nabe0001', + homebranch => 'CPL', + holdingbranch => 'CPL', + price => '0.50', + replacementprice => '0.50', + itype => 'BK', + biblioisbn => '9519671580', + itemcallnumber => 'PK 84.2', + }, + ], 'barcode', $subtestContext, undef, $testContext); + + is($objects->{'167Nabe0001'}->barcode, '167Nabe0001', "Item '167Nabe0001'."); + ##Add one more to test incrementing the subtestContext. + $objects = $f->createTestGroup([ + {biblionumber => $biblios->{9519671580}->{biblionumber}, + barcode => '167Nabe0002', + homebranch => 'CPL', + holdingbranch => 'FFL', + price => '3.50', + replacementprice => '3.50', + itype => 'BK', + biblioisbn => '9519671580', + itemcallnumber => 'JK 84.2', + }, + ], 'barcode', $subtestContext, undef, $testContext); + + is($subtestContext->{items}->{'167Nabe0001'}->barcode, '167Nabe0001', "Item '167Nabe0001' from \$subtestContext."); + is($objects->{'167Nabe0002'}->holdingbranch, 'FFL', "Item '167Nabe0002'."); + is(ref($biblios->{9519671580}), 'MARC::Record', "Biblio 'I wish I met your mother'."); + + ##Delete objects + t::lib::TestObjects::ObjectFactory->tearDownTestContext($subtestContext); + my $object1 = Koha::Items->find({barcode => '167Nabe0001'}); + ok (not($object1), "Item '167Nabe0001' deleted"); + my $object2 = Koha::Items->find({barcode => '167Nabe0002'}); + ok (not($object2), "Item '167Nabe0002' deleted"); + my $object3 = Koha::Biblios->find({title => 'I wish I met your mother', author => "Pertti Kurikka"}); + ok (not($object2), "Biblio 'I wish I met your mother' deleted"); + + #Prepare for autoremoval. + $biblios = $fb->createTestGroup([ + {'biblio.title' => 'I wish I met your mother', + 'biblio.author' => 'Pertti Kurikka', + 'biblio.copyrightdate' => '1960', + 'biblioitems.isbn' => '9519671580', + 'biblioitems.itemtype' => 'BK', + }, + ], 'biblioitems.isbn', undef, undef, $testContext); + $objects = $f->createTestGroup([ + {biblionumber => $biblios->{9519671580}->{biblionumber}, + barcode => '167Nabe0001', + homebranch => 'CPL', + holdingbranch => 'CPL', + price => '0.50', + replacementprice => '0.50', + itype => 'BK', + biblioisbn => '9519671580', + itemcallnumber => 'PK 84.2', + }, + {biblionumber => $biblios->{9519671580}->{biblionumber}, + barcode => '167Nabe0002', + homebranch => 'CPL', + holdingbranch => 'FFL', + price => '3.50', + replacementprice => '3.50', + itype => 'BK', + biblioisbn => '9519671580', + itemcallnumber => 'JK 84.2', + }, + ], 'barcode', undef, undef, $testContext); +}; + + + +########## CheckoutFactory subtests ########## +subtest 't::lib::TestObjects::CheckoutFactory' => sub { + my $subtestContext = {}; + ##Create and Delete using dependencies in the $testContext instantiated in previous subtests. + my $f = t::lib::TestObjects::CheckoutFactory->new(); + my $objects = $f->createTestGroup([ + { + cardnumber => '11A001', + barcode => '167Nabe0001', + daysOverdue => 7, + daysAgoCheckedout => 28, + }, + { + cardnumber => '11A002', + barcode => '167Nabe0002', + daysOverdue => -7, + daysAgoCheckedout => 14, + }, + ], undef, undef, undef, undef); + + is(Koha::DateUtils::dt_from_string($objects->{'11A001-167Nabe0001'}->issuedate)->day(), + DateTime->now(time_zone => C4::Context->tz())->subtract(days => '28')->day() + , "Checkout '11A001-167Nabe0001', adjusted issuedates match."); + is(Koha::DateUtils::dt_from_string($objects->{'11A002-167Nabe0002'}->date_due)->day(), + DateTime->now(time_zone => C4::Context->tz())->subtract(days => '-7')->day() + , "Checkout '11A002-167Nabe0002', adjusted date_dues match."); + + $f->deleteTestGroup($objects); + my $object1 = Koha::Checkouts->find({borrowernumber => $objects->{'11A001-167Nabe0001'}->borrowernumber, + itemnumber => $objects->{'11A001-167Nabe0001'}->itemnumber}); + ok (not($object1), "Checkout '11A001-167Nabe0001' deleted"); + my $object2 = Koha::Checkouts->find({borrowernumber => $objects->{'11A002-167Nabe0002'}->borrowernumber, + itemnumber => $objects->{'11A002-167Nabe0002'}->itemnumber}); + ok (not($object2), "Checkout '11A002-167Nabe0002' deleted"); +}; + + + +########## LetterTemplateFactory subtests ########## +subtest 't::lib::TestObjects::LetterTemplateFactory' => sub { + my $subtestContext = {}; + ##Create and Delete using dependencies in the $testContext instantiated in previous subtests. + my $f = t::lib::TestObjects::LetterTemplateFactory->new(); + my $hashLT = {letter_id => 'circulation-ODUE1-CPL-print', + module => 'circulation', + code => 'ODUE1', + branchcode => 'CPL', + name => 'Notice1', + is_html => undef, + title => 'Notice1', + message_transport_type => 'print', + content => 'Barcode: <>, bring it back!', + }; + my $objects = $f->createTestGroup([ + $hashLT, + ], undef, undef, undef, undef); + + my $letterTemplate = Koha::LetterTemplates->find($hashLT); + is($objects->{'circulation-ODUE1-CPL-print'}->name, $letterTemplate->name, "LetterTemplate 'circulation-ODUE1-CPL-print'"); + + #Delete them + $f->deleteTestGroup($objects); + $letterTemplate = Koha::LetterTemplates->find($hashLT); + ok(not(defined($letterTemplate)), "LetterTemplate 'circulation-ODUE1-CPL-print' deleted"); +}; + + + +########## Global test context subtests ########## +subtest 't::lib::TestObjects::ObjectFactory clearing global test context' => sub { + my $object11A001 = Koha::Borrowers->find({cardnumber => '11A001'}); + ok ($object11A001, "Global Borrower '11A001' exists"); + my $object11A002 = Koha::Borrowers->find({cardnumber => '11A002'}); + ok ($object11A002, "Global Borrower '11A002' exists"); + + my $object1 = Koha::Items->find({barcode => '167Nabe0001'}); + ok ($object1, "Global Item '167Nabe0001' exists"); + my $object2 = Koha::Items->find({barcode => '167Nabe0002'}); + ok ($object2, "Global Item '167Nabe0002' exists"); + my $object3 = Koha::Biblios->find({title => 'I wish I met your mother', author => "Pertti Kurikka"}); + ok ($object2, "Global Biblio 'I wish I met your mother' exists"); + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); + + $object11A001 = Koha::Borrowers->find({cardnumber => '11A001'}); + ok (not($object11A001), "Global Borrower '11A001' deleted"); + $object11A002 = Koha::Borrowers->find({cardnumber => '11A002'}); + ok (not($object11A002), "Global Borrower '11A002' deleted"); + + $object1 = Koha::Items->find({barcode => '167Nabe0001'}); + ok (not($object1), "Global Item '167Nabe0001' deleted"); + $object2 = Koha::Items->find({barcode => '167Nabe0002'}); + ok (not($object2), "Global Item '167Nabe0002' deleted"); + $object3 = Koha::Biblios->find({title => 'I wish I met your mother', author => "Pertti Kurikka"}); + ok (not($object2), "Global Biblio 'I wish I met your mother' deleted"); +}; + + + +done_testing(); + +1; -- 1.9.1