From c70e5c321d177ea20f5c667762d2aacbfe07f136 Mon Sep 17 00:00:00 2001
From: Olli-Antti Kivilahti <olli-antti.kivilahti@jns.fi>
Date: Mon, 31 Aug 2015 11:52:42 +0300
Subject: [PATCH] Bug 13906 - TestObjectFactoreis squashables

---
 C4/Installer/PerlDependencies.pm                   |   5 +
 .../Acquisition/Bookseller/ContactFactory.pm       |   3 +
 t/lib/TestObjects/Acquisition/BooksellerFactory.pm |   3 +
 t/lib/TestObjects/BiblioFactory.pm                 |  24 ++++-
 t/lib/TestObjects/BorrowerFactory.pm               |  44 +++++---
 t/lib/TestObjects/CheckoutFactory.pm               |  38 ++++---
 t/lib/TestObjects/FileFactory.pm                   | 118 +++++++++++++++++++++
 t/lib/TestObjects/ItemFactory.pm                   |  30 +++++-
 t/lib/TestObjects/LetterTemplateFactory.pm         |   3 +
 t/lib/TestObjects/ObjectFactory.pm                 |  96 +++++++++++++++--
 t/lib/TestObjects/Serial/FrequencyFactory.pm       |   3 +
 t/lib/TestObjects/Serial/SubscriptionFactory.pm    |  23 ++--
 t/lib/TestObjects/SystemPreferenceFactory.pm       |   3 +
 t/lib/TestObjects/objectFactories.t                |  95 ++++++++++++++---
 14 files changed, 429 insertions(+), 59 deletions(-)
 create mode 100644 t/lib/TestObjects/FileFactory.pm

diff --git a/C4/Installer/PerlDependencies.pm b/C4/Installer/PerlDependencies.pm
index f7a1bd9..d1a5a08 100644
--- a/C4/Installer/PerlDependencies.pm
+++ b/C4/Installer/PerlDependencies.pm
@@ -637,6 +637,11 @@ our $PERL_DEPS = {
         'required' => '0',
         'min_ver'  => '2.07',
     },
+    'File::Fu::File' => {
+        'usage'    => 'Core',
+        'required' => '1',
+        'min_ver'  => '0.0.8',
+    },
     'Archive::Extract' => {
         'usage'    => 'Plugins',
         'required' => '0',
diff --git a/t/lib/TestObjects/Acquisition/Bookseller/ContactFactory.pm b/t/lib/TestObjects/Acquisition/Bookseller/ContactFactory.pm
index ed117ef..6385e6d 100644
--- a/t/lib/TestObjects/Acquisition/Bookseller/ContactFactory.pm
+++ b/t/lib/TestObjects/Acquisition/Bookseller/ContactFactory.pm
@@ -38,6 +38,9 @@ sub new {
 sub getDefaultHashKey {
     return 'name';
 }
+sub getObjectType {
+    return 'Koha::Acquisition::Bookseller::Contact';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 @OVERLOADED
diff --git a/t/lib/TestObjects/Acquisition/BooksellerFactory.pm b/t/lib/TestObjects/Acquisition/BooksellerFactory.pm
index ecdb298..89cbb6e 100644
--- a/t/lib/TestObjects/Acquisition/BooksellerFactory.pm
+++ b/t/lib/TestObjects/Acquisition/BooksellerFactory.pm
@@ -38,6 +38,9 @@ sub new {
 sub getDefaultHashKey {
     return 'name';
 }
+sub getObjectType {
+    return 'Koha::Acquisition::Bookseller2';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 @OVERLOADED
diff --git a/t/lib/TestObjects/BiblioFactory.pm b/t/lib/TestObjects/BiblioFactory.pm
index b96cf4e..11874e7 100644
--- a/t/lib/TestObjects/BiblioFactory.pm
+++ b/t/lib/TestObjects/BiblioFactory.pm
@@ -22,6 +22,7 @@ use Modern::Perl;
 use Carp;
 
 use C4::Biblio;
+use Koha::Database;
 
 use Koha::Exception::BadParameter;
 
@@ -30,6 +31,9 @@ use base qw(t::lib::TestObjects::ObjectFactory);
 sub getDefaultHashKey {
     return 'biblioitems.isbn';
 }
+sub getObjectType {
+    return 'MARC::Record';
+}
 
 =head t::lib::TestObjects::createTestGroup
 
@@ -50,6 +54,9 @@ The biblionumber is injected to the MARC::Record-object to be easily accessable,
 so we can get it like this:
     $records->{$key}->{biblionumber};
 
+There is a duplication check to first look for Records with the same ISBN.
+If a matching ISBN is found, then we use the existing Record instead of adding a new one.
+
 See C4::Biblio::TransformKohaToMarc() for how the biblio- or biblioitem-tables'
 columns need to be given.
 
@@ -61,14 +68,23 @@ See t::lib::TestObjects::ObjectFactory for more documentation
 sub handleTestObject {
     my ($class, $object, $stashes) = @_;
 
-    my $record = C4::Biblio::TransformKohaToMarc($object);
-    my ($biblionumber, $biblioitemnumber) = C4::Biblio::AddBiblio($record,'');
+    ##First see if the given Record already exists in the DB. For testing purposes we use the isbn as the UNIQUE identifier.
+    my $resultset = Koha::Database->new()->schema()->resultset('Biblioitem');
+    my $existingBiblio = $resultset->search({isbn => $object->{"biblioitems.isbn"}})->next();
+    my ($record, $biblionumber, $biblioitemnumber);
+    unless ($existingBiblio) {
+        $record = C4::Biblio::TransformKohaToMarc($object);
+        ($biblionumber, $biblioitemnumber) = C4::Biblio::AddBiblio($record,'');
+    }
+    else {
+        $record = C4::Biblio::GetMarcBiblio($existingBiblio->biblionumber->biblionumber); #Funny!
+    }
 
     #Clone all the parameters of $object to $record
     foreach my $key (keys(%$object)) {
         $record->{$key} = $object->{$key};
     }
-    $record->{biblionumber} = $biblionumber;
+    $record->{biblionumber} = $biblionumber || $existingBiblio->biblionumber->biblionumber;
 
     return $record;
 }
@@ -109,6 +125,8 @@ sub validateAndPopulateDefaultValues {
     unless ($object->{'biblio.title'}) {
         Koha::Exception::BadParameter->throw(error => __PACKAGE__."->createTestGroup():> 'biblio.title' is a mandatory parameter!");
     }
+    $object->{'biblioitems.isbn'} = '971-972-call-me' unless $object->{'biblioitems.isbn'};
+
     $self->SUPER::validateAndPopulateDefaultValues($object, $hashKey);
 }
 
diff --git a/t/lib/TestObjects/BorrowerFactory.pm b/t/lib/TestObjects/BorrowerFactory.pm
index 1d8da41..797e1d0 100644
--- a/t/lib/TestObjects/BorrowerFactory.pm
+++ b/t/lib/TestObjects/BorrowerFactory.pm
@@ -21,6 +21,7 @@ package t::lib::TestObjects::BorrowerFactory;
 use Modern::Perl;
 use Carp;
 use Scalar::Util qw(blessed);
+use Encode;
 
 use C4::Members;
 use Koha::Borrowers;
@@ -38,6 +39,9 @@ sub new {
 sub getDefaultHashKey {
     return 'cardnumber';
 }
+sub getObjectType {
+    return 'Koha::Borrower';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 @OVERLOADED
@@ -79,26 +83,33 @@ See t::lib::TestObjects::ObjectFactory for more documentation
 sub handleTestObject {
     my ($class, $object, $stashes) = @_;
 
-    #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;
+    my $borrower;
     eval {
-        $borrowernumber = C4::Members::AddMember(%$object);
+        $borrower = Koha::Borrowers->cast($object); #Try getting the borrower first
     };
-    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 $@;
+
+    my $borrowernumber;
+    unless ($borrower) {
+        #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.
+        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.
+        $borrower = Koha::Borrowers->cast( $borrowernumber || $object );
     }
 
-    #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}."'";
         return();
@@ -118,6 +129,9 @@ sub validateAndPopulateDefaultValues {
     my ($self, $borrower, $hashKey) = @_;
     $self->SUPER::validateAndPopulateDefaultValues($borrower, $hashKey);
 
+    $borrower->{firstname} = 'Maija' unless $borrower->{firstname};
+    $borrower->{surname} = Encode::decode('UTF-8', 'Meikäläinen') unless $borrower->{surname};
+    $borrower->{cardnumber} = '167A000001TEST' unless $borrower->{cardnumber};
     $borrower->{categorycode} = 'PT' unless $borrower->{categorycode};
     $borrower->{branchcode}   = 'CPL' unless $borrower->{branchcode};
     $borrower->{dateofbirth}  = '1985-10-12' unless $borrower->{dateofbirth};
diff --git a/t/lib/TestObjects/CheckoutFactory.pm b/t/lib/TestObjects/CheckoutFactory.pm
index bb9a2d1..542f12a 100644
--- a/t/lib/TestObjects/CheckoutFactory.pm
+++ b/t/lib/TestObjects/CheckoutFactory.pm
@@ -27,6 +27,10 @@ use C4::Members;
 use C4::Items;
 use Koha::Borrowers;
 use Koha::Items;
+use Koha::Checkouts;
+
+use t::lib::TestObjects::BorrowerFactory;
+use t::lib::TestObjects::ItemFactory;
 
 use base qw(t::lib::TestObjects::ObjectFactory);
 
@@ -41,6 +45,9 @@ sub new {
 sub getDefaultHashKey {
     return ['cardnumber', 'barcode'];
 }
+sub getObjectType {
+    return 'Koha::Checkout';
+}
 
 =head t::lib::TestObjects::CheckoutFactory::createTestGroup( $data [, $hashKey], @stashes )
 
@@ -75,15 +82,12 @@ sub getDefaultHashKey {
 @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.
+@RETURNS HASHRef of $hashKey => Koha::Checkout-objects.
                 The HASH is keyed with <cardnumber>-<barcode>, or the given $hashKey.
     Example: {
-    '11A001-167N0212' => {
-        cardnumber => <cardnumber>,
-        barcode => <barcode>,
-        ...other Checkout-object columns...
-    },
-    ...
+        '11A001-167N0212' => Koha::Checkout,
+        ...
+    }
 }
 =cut
 
@@ -100,8 +104,21 @@ sub handleTestObject {
     }
     my $oldContextBranch = C4::Context->userenv()->{branch};
 
-    my $borrower = Koha::Borrowers->cast($checkoutParams->{cardnumber});
-    my $item =     Koha::Items->cast($checkoutParams->{barcode});
+    my $borrower = Koha::Borrowers->find({cardnumber => $checkoutParams->{cardnumber}});
+    unless($borrower) {
+        my $borrowers = t::lib::TestObjects::BorrowerFactory->createTestGroup(
+                                {cardnumber => $checkoutParams->{cardnumber}},
+                                undef, @$stashes);
+        $borrower = $borrowers->{ $checkoutParams->{cardnumber} };
+    }
+
+    my $item =     Koha::Items->find({barcode => $checkoutParams->{barcode}});
+    unless($item) {
+        my $items = t::lib::TestObjects::ItemFactory->createTestGroup(
+                                {barcode => $checkoutParams->{barcode}},
+                                undef, @$stashes);
+        $item = $items->{ $checkoutParams->{barcode} };
+    }
 
     my $duedate = DateTime->now(time_zone => C4::Context->tz());
     if ($checkoutParams->{daysOverdue}) {
@@ -155,12 +172,9 @@ sub validateAndPopulateDefaultValues {
     unless ($object->{cardnumber}) {
         croak __PACKAGE__.":> Mandatory parameter 'cardnumber' missing.";
     }
-    $object->{borrower} = Koha::Borrowers->cast($object->{cardnumber});
-
     unless ($object->{barcode}) {
         croak __PACKAGE__.":> Mandatory parameter 'barcode' missing.";
     }
-    $object->{item} = Koha::Items->cast($object->{barcode});
 
     if ($object->{checkoutBranchRule} && not($object->{checkoutBranchRule} =~ m/(homebranch)|(holdingbranch)/)) {
         croak __PACKAGE__.":> Optional parameter 'checkoutBranchRule' must be one of these: homebranch, holdingbranch";
diff --git a/t/lib/TestObjects/FileFactory.pm b/t/lib/TestObjects/FileFactory.pm
new file mode 100644
index 0000000..7bbd1ef
--- /dev/null
+++ b/t/lib/TestObjects/FileFactory.pm
@@ -0,0 +1,118 @@
+package t::lib::TestObjects::FileFactory;
+
+# 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 File::Spec;
+use File::Path;
+
+use Koha::AtomicUpdater;
+use Koha::Database;
+use File::Fu::File;
+
+use Koha::Exception::BadParameter;
+
+use base qw(t::lib::TestObjects::ObjectFactory);
+
+my $tmpdir = File::Spec->tmpdir();
+
+sub getDefaultHashKey {
+    return 'OVERLOADED';
+}
+sub getObjectType {
+    return 'File::Fu::File';
+}
+
+=head t::lib::TestObjects::createTestGroup
+
+    my $files = t::lib::TestObjects::FileFactory->createTestGroup([
+                    {'filepath' => 'atomicupdate/', #this is prepended with the system's default tmp directory, usually /tmp/
+                     'filename' => '#30-RabiesIsMyDog.pl',
+                     'content' => 'print "Mermaids are my only love\nI never let them down";',
+                    },
+                ], ['filepath', 'filename'], $testContext1, $testContext2, $testContext3);
+
+Calls Koha::FileFactory to add files with content to your system, and clean up automatically.
+
+The HASH is keyed with the 'filename', or the given $hashKeys.
+
+@RETURNS HASHRef of File::Fu::File-objects
+
+See t::lib::TestObjects::ObjectFactory for more documentation
+=cut
+
+sub handleTestObject {
+    my ($class, $object, $stashes) = @_;
+
+    my $absolutePath = $tmpdir.'/'.$object->{filepath};
+    File::Path::make_path($absolutePath);
+    my $file = File::Fu::File->new($absolutePath.'/'.$object->{filename});
+
+    $file->write($object->{content}) if $object->{content};
+
+    return $file;
+}
+
+=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, $object, $hashKey) = @_;
+
+    foreach my $param (('filename', 'filepath')) {
+        unless ($object->{$param}) {
+            Koha::Exception::BadParameter->throw(
+                error => __PACKAGE__."->validateAndPopulateDefaultValues():> parameter '$param' is mandatory.");
+        }
+        if ($object->{$param} =~ m/(\$|\.\.|~|\s)/) {
+            Koha::Exception::BadParameter->throw(
+                error => __PACKAGE__."->validateAndPopulateDefaultValues():> parameter '$param' as '".$object->{$param}."'.".
+                         'Disallowed characters present  ..  ~  $ + whitespace');
+        }
+    }
+}
+
+sub deleteTestGroup {
+    my ($class, $objects) = @_;
+
+    while( my ($key, $object) = each %$objects) {
+        $object->remove if $object->e;
+        #We could as well remove the complete subfolder but I am too afraid to automate "rm -r" here
+    }
+}
+
+=head getHashKey
+@OVERLOADED
+
+@RETURNS String, The test context/stash HASH key to differentiate this object
+                 from all other such test objects.
+=cut
+
+sub getHashKey {
+    my ($class, $fileObject, $primaryKey, $hashKeys) = @_;
+
+    return $fileObject->get_file();
+}
+
+1;
diff --git a/t/lib/TestObjects/ItemFactory.pm b/t/lib/TestObjects/ItemFactory.pm
index ab33802..f75fb97 100644
--- a/t/lib/TestObjects/ItemFactory.pm
+++ b/t/lib/TestObjects/ItemFactory.pm
@@ -22,9 +22,12 @@ use Modern::Perl;
 use Carp;
 
 use C4::Items;
+use Koha::Biblios;
 use Koha::Items;
 use Koha::Checkouts;
 
+use t::lib::TestObjects::BiblioFactory;
+
 use base qw(t::lib::TestObjects::ObjectFactory);
 
 sub new {
@@ -38,6 +41,9 @@ sub new {
 sub getDefaultHashKey {
     return 'barcode';
 }
+sub getObjectType {
+    return 'Koha::Item';
+}
 
 =head t::lib::TestObjects::ItemFactory::createTestGroup( $data [, $hashKey] )
 @OVERLOADED
@@ -56,9 +62,29 @@ See t::lib::TestObjects::ObjectFactory for more documentation
 sub handleTestObject {
     my ($class, $object, $stashes) = @_;
 
+    #Look for the parent biblio, if we don't find one, create a default one.
     my ($biblionumber, $biblioitemnumber, $itemnumber);
+    my $biblio;
+    $biblio = Koha::Biblios->find({biblionumber => $object->{biblionumber}}) if $object->{biblionumber};
+    unless ($biblio) {
+        my $biblios = t::lib::TestObjects::BiblioFactory->createTestGroup({"biblio.title" => "Test Items' Biblio",},
+                                                                          undef, @$stashes);
+        $biblio = $biblios->{'971-972-call-me'};
+        $object->{biblionumber} = $biblio->{biblionumber};
+    }
+    else {
+        $object->{biblionumber} = $biblio->biblionumber;
+    }
+
+    #Ok we got a biblio, now we can add an Item for it. First see if the Item already exists.
+    my $item;
     eval {
-        ($biblionumber, $biblioitemnumber, $itemnumber) = C4::Items::AddItem($object, $object->{biblionumber});
+        eval {
+            $item = Koha::Items->cast($object);
+        };
+        unless ($item) {
+            ($biblionumber, $biblioitemnumber, $itemnumber) = C4::Items::AddItem($object, $object->{biblionumber});
+        }
     };
     if ($@) {
         if (blessed($@) && $@->isa('DBIx::Class::Exception') &&
@@ -70,7 +96,7 @@ sub handleTestObject {
             die $@;
         }
     }
-    my $item = Koha::Items->cast($itemnumber || $object);
+    $item = Koha::Items->cast($itemnumber || $object) unless $item;
     unless ($item) {
         carp "ItemFactory:> No item for barcode '".$object->{barcode}."'";
         next();
diff --git a/t/lib/TestObjects/LetterTemplateFactory.pm b/t/lib/TestObjects/LetterTemplateFactory.pm
index 6a6d4fd..8214ba6 100644
--- a/t/lib/TestObjects/LetterTemplateFactory.pm
+++ b/t/lib/TestObjects/LetterTemplateFactory.pm
@@ -37,6 +37,9 @@ sub new {
 sub getDefaultHashKey {
     return ['module', 'code', 'branchcode', 'message_transport_type'];
 }
+sub getObjectType {
+    return 'Koha::LetterTemplate';
+}
 
 =head t::lib::TestObjects::LetterTemplateFactory->createTestGroup
 Returns a HASH of Koha::LetterTemplate-objects
diff --git a/t/lib/TestObjects/ObjectFactory.pm b/t/lib/TestObjects/ObjectFactory.pm
index 34cb90d..e4f6441 100644
--- a/t/lib/TestObjects/ObjectFactory.pm
+++ b/t/lib/TestObjects/ObjectFactory.pm
@@ -79,7 +79,8 @@ sub createTestGroup {
 
         my $addedObject = $class->handleTestObject($o, $stashes);
         if (not($addedObject) ||
-            not(ref($addedObject) eq 'HASH' || $addedObject->isa('Koha::Object') || $addedObject->isa('MARC::Record') )
+            (blessed($addedObject) && not($addedObject->isa($class->getObjectType()))) ||
+            not(ref($addedObject) eq $class->getObjectType() )
            ) {
             Koha::Exception::UnknownObject->throw(error => __PACKAGE__."->createTestGroup():> Subroutine '$class->handleTestObject()' must return a HASH or a Koha::Object");
         }
@@ -130,6 +131,11 @@ sub tearDownTestContext {
     my ($self, $stash) = @_;
 
     ##You should introduce tearDowns in such an order that to not provoke FOREIGN KEY issues.
+    if ($stash->{'file'}) {
+        require t::lib::TestObjects::FileFactory;
+        t::lib::TestObjects::FileFactory->deleteTestGroup($stash->{'file'});
+        delete $stash->{'file'};
+    }
     if ($stash->{'serial-subscription'}) {
         require t::lib::TestObjects::Serial::SubscriptionFactory;
         t::lib::TestObjects::Serial::SubscriptionFactory->deleteTestGroup($stash->{'serial-subscription'});
@@ -206,6 +212,33 @@ sub getHashKey {
     return join('-', @collectedHashKeys);
 }
 
+=head
+
+=cut
+
+sub addToContext {
+    my ($class, $objects, $hashKeys, $featureStash, $scenarioStash, $stepStash) = @_;
+    my @stashes = ($featureStash, $scenarioStash, $stepStash);
+
+    if (ref($objects) eq 'ARRAY') {
+        foreach my $object (@$objects) {
+            $class->addToContext($object, $hashKeys, @stashes);
+        }
+        return undef; #End recursion
+    }
+    elsif (ref($objects) eq 'HASH') {
+        #Apparently we get a HASH of keyed objects.
+        $class->_persistToStashes($objects, $class->getHashGroupName(), @stashes);
+        return undef; #End recursion
+    }
+    else {
+        #Here $objects is verified to be a single object, instead of a group of objects.
+        #We create a hash key for it and append it to the stashes.
+        my $hash = { $class->getHashKey($objects, undef, $class->getDefaultHashKey) => $objects};
+        $class->_persistToStashes($hash, $class->getHashGroupName(), @stashes);
+    }
+}
+
 =head validateAndPopulateDefaultValues
 @INTERFACE
 
@@ -225,6 +258,54 @@ sub validateAndPopulateDefaultValues {
     }
 }
 
+=head validateObjectType
+
+    try {
+        $object = $class->validateObjectType($object);
+    } catch {
+        ...
+    }
+
+Validates if the given Object matches the expected type of the subclassing TestObjectFactory.
+@PARAM1 Object that needs to be validated.
+@THROWS Koha::Exception::UnknownObject, if the given object is not of the same type that the object factory creates.
+
+=cut
+
+sub validateObjectType {
+    my ($class, $object) = @_;
+
+    my $invalid = 0;
+    if (blessed($object)) {
+        unless ($object->isa( $class->getObjectType() )) {
+            $invalid = 1;
+        }
+    }
+    else {
+        unless (ref($object) eq $class->getObjectType()) {
+            $invalid = 1;
+        }
+    }
+
+    Koha::Exception::UnknownObject->throw(
+        error => "$class->validateObjectType():> Given object '$object' isn't a '".$class->getObjectType()."'-object."
+    ) if $invalid;
+
+    return $object;
+}
+
+=head getObjectType
+@OVERLOAD
+Get the type of objects this factory creates.
+@RETURN String, the object package this factory creates. eg. Koha::Borrower
+=cut
+
+sub getObjectType {
+    my ($class) = @_;
+    die "You must overload 'validateObjectType()' in the implementing ObjectFactory subclass '$class'.";
+    return 'Koha::Object derivative or other Object';
+}
+
 =head _validateStashes
 
     _validateStashes($featureStash, $scenarioStash, $stepStash);
@@ -255,15 +336,16 @@ Saves the given HASH to the given stashes using the given stash key.
 =cut
 
 sub _persistToStashes {
-    my ($self, $objects, $stashKey, $featureStash, $scenarioStash, $stepStash) = @_;
+    my ($class, $objects, $stashKey, $featureStash, $scenarioStash, $stepStash) = @_;
 
     if ($featureStash || $scenarioStash || $stepStash) {
-        while( my ($key, $biblio) = each %$objects) {
-            $featureStash->{$stashKey}->{ $key }  = $biblio if $featureStash;
-            $scenarioStash->{$stashKey}->{ $key } = $biblio if $scenarioStash;
-            $stepStash->{$stashKey}->{ $key }     = $biblio if $stepStash;
+        while( my ($key, $object) = each %$objects) {
+            $class->validateObjectType($object); #Make sure we put in what we are expected to
+            $featureStash->{$stashKey}->{ $key }  = $object if $featureStash;
+            $scenarioStash->{$stashKey}->{ $key } = $object if $scenarioStash;
+            $stepStash->{$stashKey}->{ $key }     = $object if $stepStash;
         }
     }
 }
 
-1;
\ No newline at end of file
+1;
diff --git a/t/lib/TestObjects/Serial/FrequencyFactory.pm b/t/lib/TestObjects/Serial/FrequencyFactory.pm
index da48246..09889d3 100644
--- a/t/lib/TestObjects/Serial/FrequencyFactory.pm
+++ b/t/lib/TestObjects/Serial/FrequencyFactory.pm
@@ -38,6 +38,9 @@ sub new {
 sub getDefaultHashKey {
     return 'description';
 }
+sub getObjectType {
+    return 'Koha::Serial::Subscription::Frequency';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 @OVERLOADED
diff --git a/t/lib/TestObjects/Serial/SubscriptionFactory.pm b/t/lib/TestObjects/Serial/SubscriptionFactory.pm
index f09514b..521ce90 100644
--- a/t/lib/TestObjects/Serial/SubscriptionFactory.pm
+++ b/t/lib/TestObjects/Serial/SubscriptionFactory.pm
@@ -21,7 +21,9 @@ package t::lib::TestObjects::Serial::SubscriptionFactory;
 use Modern::Perl;
 use Carp;
 use Scalar::Util qw(blessed);
+use DateTime;
 
+use C4::Context;
 use C4::Serials;
 
 use t::lib::TestObjects::BorrowerFactory;
@@ -40,6 +42,9 @@ use base qw(t::lib::TestObjects::ObjectFactory);
 sub getDefaultHashKey {
     return 'internalnotes';
 }
+sub getObjectType {
+    return 'Koha::Serial::Subscription';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 
@@ -54,7 +59,7 @@ sub getDefaultHashKey {
                 aqbudgetid => undef, #DEFAULT
                 biblio => 21 || Koha::Biblio, #DEFAULT creates a "Serial magazine" Record
                 startdate => '2015-01-01', #DEFAULTs to 1.1 this year, so the subscription is active by default.
-                periodicity => 7 || Koha::Serial::Subscription::Frequency, #DEFAULTS to a Frequency of 1/month.
+                periodicity => 2 || Koha::Serial::Subscription::Frequency, #DEFAULTS to a Frequency of 1/week.
                 numberlength => 12, #DEFAULT one year subscription, only one of ['numberlength', 'weeklength', 'monthlength'] is needed
                 weeklength => 52, #DEFAULT one year subscription
                 monthlength => 12, #DEFAULT one year subscription
@@ -170,6 +175,10 @@ and populates defaults for missing values.
 sub validateAndPopulateDefaultValues {
     my ($self, $object, $hashKey, $stashes) = @_;
 
+    #Get this year so we can use it to populate always active Objects.
+    my $now = DateTime->now(time_zone => C4::Context->tz());
+    my $year = $now->year();
+
     if ($object->{librarian}) {
         $object->{librarian} = Koha::Borrowers->cast($object->{librarian});
     }
@@ -195,27 +204,27 @@ sub validateAndPopulateDefaultValues {
         $object->{biblio} = t::lib::TestObjects::BiblioFactory->createTestGroup([
                                     {'biblio.title' => 'Serial magazine',
                                      'biblio.author'   => 'Pertti Kurikka',
-                                     'biblio.copyrightdate' => '2015',
+                                     'biblio.copyrightdate' => $year,
                                      'biblioitems.isbn'     => 'isbnisnotsocoolnowadays!',
                                      'biblioitems.itemtype' => 'CR',
                                     },
                                 ], undef, @$stashes)
                                 ->{'isbnisnotsocoolnowadays!'};
     }
-
     unless ($object->{internalnotes}) {
         croak __PACKAGE__.":> Mandatory parameter 'internalnotes' missing. This is used as the returning hash-key!";
     }
-    $object->{periodicity}   = 7 unless $object->{periodicity};
+
+    $object->{periodicity}   = 4 unless $object->{periodicity};
     $object->{numberpattern} = 2 unless $object->{numberpattern};
     $object->{branchcode}    = 'CPL' unless $object->{branchcode};
     $object->{cost}          = undef unless $object->{cost};
     $object->{aqbudgetid}    = undef unless $object->{aqbudgetid};
-    $object->{startdate}     = '2015-01-01' unless $object->{startdate};
+    $object->{startdate}     = "$year-01-01" unless $object->{startdate};
     $object->{numberlength}  = undef unless $object->{numberlength};
     $object->{weeklength}    = undef unless $object->{weeklength} || $object->{numberlength};
     $object->{monthlength}   = 12 unless $object->{monthlength} || $object->{weeklength} || $object->{numberlength};
-    $object->{lastvalue1}    = 2015 unless $object->{lastvalue1};
+    $object->{lastvalue1}    = $year unless $object->{lastvalue1};
     $object->{innerloop1}    = undef unless $object->{innerloop1};
     $object->{lastvalue2}    = 1 unless $object->{lastvalue2};
     $object->{innerloop2}    = undef unless $object->{innerloop2};
@@ -224,7 +233,7 @@ sub validateAndPopulateDefaultValues {
     $object->{status}        = 1 unless $object->{status};
     $object->{notes}         = 'Public note' unless $object->{notes};
     $object->{letter}        = 'RLIST' unless $object->{letter};
-    $object->{firstacquidate} = '2015-01-01' unless $object->{firstacquidate};
+    $object->{firstacquidate} = "$year-01-01" unless $object->{firstacquidate};
     $object->{irregularity}  = undef unless $object->{irregularity};
     $object->{locale}        = undef unless $object->{locale};
     $object->{callnumber}    = 'MAG 10.2 AZ' unless $object->{callnumber};
diff --git a/t/lib/TestObjects/SystemPreferenceFactory.pm b/t/lib/TestObjects/SystemPreferenceFactory.pm
index 8b58eba..81f4d3c 100644
--- a/t/lib/TestObjects/SystemPreferenceFactory.pm
+++ b/t/lib/TestObjects/SystemPreferenceFactory.pm
@@ -40,6 +40,9 @@ sub new {
 sub getDefaultHashKey {
     return 'preference';
 }
+sub getObjectType {
+    return 'HASH';
+}
 
 =head createTestGroup( $data [, $hashKey, $testContexts...] )
 @OVERLOADED
diff --git a/t/lib/TestObjects/objectFactories.t b/t/lib/TestObjects/objectFactories.t
index 62646db..c6668b9 100644
--- a/t/lib/TestObjects/objectFactories.t
+++ b/t/lib/TestObjects/objectFactories.t
@@ -40,6 +40,9 @@ use t::lib::TestObjects::Acquisition::Bookseller::ContactFactory;
 use Koha::Acquisition::Bookseller::Contacts;
 use t::lib::TestObjects::Acquisition::BooksellerFactory;
 use Koha::Acquisition::Booksellers;
+use t::lib::TestObjects::FileFactory;
+use File::Slurp;
+use File::Fu::File;
 use t::lib::TestObjects::Serial::SubscriptionFactory;
 use Koha::Serial::Subscriptions;
 use Koha::Serial::Subscription::Frequencies;
@@ -53,24 +56,83 @@ my $testContext = {}; #Gather all created Objects here so we can finally remove
 
 
 
+########## FileFactory subtests ##########
+subtest 't::lib::TestObjects::FileFactory' => \&testFileFactory;
+sub testFileFactory {
+    my ($files);
+    my $subtestContext = {};
+
+    $files = t::lib::TestObjects::FileFactory->createTestGroup([
+                        {'filepath' => 'atomicupdate',
+                         'filename' => '#30-RabiesIsMyDog.pl',
+                         'content' => 'print "Mermaids are my only love\nI never let them down";',
+                        },
+                        {'filepath' => 'atomicupdate',
+                         'filename' => '#31-FrogsArePeopleToo.pl',
+                         'content' => 'print "Listen to the Maker!";',
+                        },
+                        {'filepath' => 'atomicupdate',
+                         'filename' => '#32-AnimalLover.pl',
+                         'content' => "print 'Do not hurt them!;",
+                        },
+                    ], undef, $subtestContext);
+
+    my $file30content = File::Slurp::read_file( $files->{'#30-RabiesIsMyDog.pl'}->absolutely );
+    ok($file30content =~ m/Mermaids are my only love/,
+       "'#30-RabiesIsMyDog.pl' created and content matches");
+    my $file31content = File::Slurp::read_file( $files->{'#31-FrogsArePeopleToo.pl'}->absolutely );
+    ok($file31content =~ m/Listen to the Maker!/,
+       "'#31-FrogsArePeopleToo.pl' created and content matches");
+    my $file32content = File::Slurp::read_file( $files->{'#32-AnimalLover.pl'}->absolutely );
+    ok($file32content =~ m/Do not hurt them!/,
+       "'#32-AnimalLover.pl' created and content matches");
+
+    ##addToContext() test, create new file
+    my $dir = $files->{'#32-AnimalLover.pl'}->dirname();
+    my $file = File::Fu::File->new("$dir/addToContext.txt");
+    $file->touch;
+    t::lib::TestObjects::FileFactory->addToContext($file, undef, $subtestContext);
+    ok($file->e,
+       "'addToContext.txt' created");
+
+    t::lib::TestObjects::ObjectFactory->tearDownTestContext($subtestContext);
+
+    ok(not(-e $files->{'#30-RabiesIsMyDog.pl'}->absolutely),
+       "'#30-RabiesIsMyDog.pl' deleted");
+    ok(not(-e $files->{'#31-FrogsArePeopleToo.pl'}->absolutely),
+       "'#31-FrogsArePeopleToo.pl' deleted");
+    ok(not(-e $files->{'#32-AnimalLover.pl'}->absolutely),
+       "'#32-AnimalLover.pl' deleted");
+    ok(not(-e $file->absolutely),
+       "'addToContext.txt' deleted");
+};
+
+
+
 ########## Serial subtests ##########
-subtest 't::lib::TestObjects::Serial' => sub {
-    my ($subscriptions, $subscription, $frequency, $numberpattern, $biblio, $borrower, $bookseller, $items, $serials);
+subtest 't::lib::TestObjects::Serial' => \&testSerialFactory;
+sub testSerialFactory {
+    my ($subscriptions, $subscription, $frequency, $numberpattern, $biblio, $sameBiblio, $borrower, $bookseller, $items, $serials);
     my $subtestContext = {};
     my $dontDeleteTestContext = {};
     ##Create and delete
     $subscriptions = t::lib::TestObjects::Serial::SubscriptionFactory->createTestGroup([
                                 {internalnotes => 'TESTDEFAULTS',
                                  receiveSerials => 3},
+                                {internalnotes => 'SAMEBIBLIO',},
                                 ], undef, $subtestContext);
     $subscription = Koha::Serial::Subscriptions->find( $subscriptions->{'TESTDEFAULTS'}->subscriptionid );
     $frequency = $subscription->periodicity();
     $numberpattern = $subscription->numberpattern();
     $biblio = $subscription->biblio();
+    $sameBiblio = $subscriptions->{SAMEBIBLIO}->biblio;
     $borrower = $subscription->borrower();
     $bookseller = $subscription->bookseller();
     $items = $subscription->items();
     $serials = $subscription->serials();
+    is($biblio->biblionumber,
+       $sameBiblio->biblionumber,
+       "Default Subscriptions use the same default Biblio");
     ok(($subscriptions->{'TESTDEFAULTS'}->callnumber eq $subscription->callnumber &&
         $subscriptions->{'TESTDEFAULTS'}->subscriptionid eq $subscription->subscriptionid),
        "Default Subscription created.");
@@ -163,7 +225,8 @@ subtest 't::lib::TestObjects::Serial' => sub {
 
 
 ########## Acquisition subtests ##########
-subtest 't::lib::TestObjects::Acquisition' => sub {
+subtest 't::lib::TestObjects::Acquisition' => \&testAcquisitionFactories;
+sub testAcquisitionFactories {
     my ($booksellers, $bookseller, $contacts, $contact);
     my $subtestContext = {};
     ##Create and delete
@@ -198,7 +261,8 @@ subtest 't::lib::TestObjects::Acquisition' => sub {
 
 
 ########## BorrowerFactory subtests ##########
-subtest 't::lib::TestObjects::BorrowerFactory' => sub {
+subtest 't::lib::TestObjects::BorrowerFactory' => \&testBorrowerFactory;
+sub testBorrowerFactory {
     my $subtestContext = {};
     ##Create and Delete. Add one
     my $f = t::lib::TestObjects::BorrowerFactory->new();
@@ -223,10 +287,10 @@ subtest 't::lib::TestObjects::BorrowerFactory' => sub {
 
     ##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");
+    foreach my $cn (('11A001', '11A002')) {
+        ok (not(Koha::Borrowers->find({cardnumber => $cn})),
+            "Borrower '11A001' deleted");
+    }
 
     #Prepare for global autoremoval.
     $objects = $f->createTestGroup([
@@ -246,7 +310,8 @@ subtest 't::lib::TestObjects::BorrowerFactory' => sub {
 
 
 ########## BiblioFactory and ItemFactory subtests ##########
-subtest 't::lib::TestObjects::BiblioFactory and ::ItemFactory' => sub {
+subtest 't::lib::TestObjects::BiblioFactory and ::ItemFactory' => \&testBiblioItemFactories;
+sub testBiblioItemFactories {
     my $subtestContext = {};
     ##Create and Delete. Add one
     my $biblios = t::lib::TestObjects::BiblioFactory->createTestGroup([
@@ -302,7 +367,8 @@ subtest 't::lib::TestObjects::BiblioFactory and ::ItemFactory' => sub {
 
 
 ########## CheckoutFactory subtests ##########
-subtest 't::lib::TestObjects::CheckoutFactory' => sub {
+subtest 't::lib::TestObjects::CheckoutFactory' => \&testCheckoutFactory;
+sub testCheckoutFactory {
     my $subtestContext = {};
     ##Create and Delete using dependencies in the $testContext instantiated in previous subtests.
     my $biblios = t::lib::TestObjects::BiblioFactory->createTestGroup([
@@ -376,7 +442,8 @@ subtest 't::lib::TestObjects::CheckoutFactory' => sub {
 
 
 ########## LetterTemplateFactory subtests ##########
-subtest 't::lib::TestObjects::LetterTemplateFactory' => sub {
+subtest 't::lib::TestObjects::LetterTemplateFactory' => \&testLetterTemplateFactory;
+sub testLetterTemplateFactory {
     my $subtestContext = {};
     ##Create and Delete using dependencies in the $testContext instantiated in previous subtests.
     my $f = t::lib::TestObjects::LetterTemplateFactory->new();
@@ -406,7 +473,8 @@ subtest 't::lib::TestObjects::LetterTemplateFactory' => sub {
 
 
 ########## SystemPreferenceFactory subtests ##########
-subtest 't::lib::TestObjects::SystemPreferenceFactory' => sub {
+subtest 't::lib::TestObjects::SystemPreferenceFactory' => \&testSystemPreferenceFactory;
+sub testSystemPreferenceFactory {
     my $subtestContext = {};
 
     # take syspref 'opacuserlogin' and save its current value
@@ -443,7 +511,8 @@ subtest 't::lib::TestObjects::SystemPreferenceFactory' => sub {
 
 
 ########## Global test context subtests ##########
-subtest 't::lib::TestObjects::ObjectFactory clearing global test context' => sub {
+subtest 't::lib::TestObjects::ObjectFactory clearing global test context' => \&testGlobalSubtestContext;
+sub testGlobalSubtestContext {
     my $object11A001 = Koha::Borrowers->find({cardnumber => '11A001'});
     ok ($object11A001, "Global Borrower '11A001' exists");
     my $object11A002 = Koha::Borrowers->find({cardnumber => '11A002'});
-- 
1.9.1