@@ -, +, @@ been applied to a database --- C4/Installer/PerlDependencies.pm | 5 + Koha/AtomicUpdate.pm | 121 +++++++++++ Koha/AtomicUpdater.pm | 223 +++++++++++++++++++++ installer/data/mysql/atomicupdate.pl | 96 +++++++++ .../mysql/atomicupdate/Bug14698-AtomicUpdater.pl | 36 ++++ installer/data/mysql/kohastructure.sql | 14 ++ t/db_dependent/Koha/AtomicUpdater.t | 200 ++++++++++++++++++ t/lib/TestObjects/AtomicUpdateFactory.pm | 105 ++++++++++ t/lib/TestObjects/ObjectFactory.pm | 5 + t/lib/TestObjects/objectFactories.t | 39 ++++ 10 files changed, 844 insertions(+) create mode 100644 Koha/AtomicUpdate.pm create mode 100644 Koha/AtomicUpdater.pm create mode 100644 installer/data/mysql/atomicupdate.pl create mode 100644 installer/data/mysql/atomicupdate/Bug14698-AtomicUpdater.pl create mode 100644 t/db_dependent/Koha/AtomicUpdater.t create mode 100644 t/lib/TestObjects/AtomicUpdateFactory.pm --- a/C4/Installer/PerlDependencies.pm +++ a/C4/Installer/PerlDependencies.pm @@ -232,6 +232,11 @@ our $PERL_DEPS = { 'min_ver' => '0.45', # Also needed for our use of PDF::Reuse }, + 'Data::Format::Pretty::Console' => { + 'usage' => 'Core', + 'required' => '1', + 'min_ver' => '0.34', + }, 'DateTime' => { 'usage' => 'Core', 'required' => '1', --- a/Koha/AtomicUpdate.pm +++ a/Koha/AtomicUpdate.pm @@ -0,0 +1,121 @@ +package Koha::AtomicUpdate; + +# Copyright Open Source Freedom Fighters +# +# 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::Basename; + +use Koha::Database; + +use base qw(Koha::Object); + +use Koha::Exception::BadParameter; + +sub type { + return 'Atomicupdate'; +} + +=head @allowedIssueIdentifierPrefixes +Define the prefixes you want to attach to your atomicupdate filenames here. +This could be a syspref or in KOHA_CONF, but it is rather easy to just add more +generally used issue number prefixes here. +Nobody wants more sysprefs. +=cut + +my @allowedIssueIdentifierPrefixes = ( + 'Bug', + '#', +); + +sub new { + my ($class, $params) = @_; + $class->_validateParams($params); + + my $self = {}; + bless($self, $class); + $self->set($params); + return $self; +} + +sub _validateParams { + my ($class, $params) = @_; + + my @mandatoryParams = ('filename'); + foreach my $mp (@mandatoryParams) { + Koha::Exception::BadParameter->throw( + error => "$class->_validateParams():> Param '$mp' must be given.") + unless($params->{$mp}); + } + $params->{filename} = $class->_validateFilename($params->{filename}); + + $params->{issue_id} = $class->_getIssueIdentifier($params->{issue_id} || $params->{filename}); +} + +=head _validateFilename + +Makes sure the given file is a valid AtomicUpdate-script. +Currently simply checks for naming convention and file suffix. + +NAMING CONVENTION: + Filename must contain one of the unique issue identifier prefixes from this + list @allowedIssueIdentifierPrefixes immediately followed by the numeric + id of the issue, optionally separated by any of the following [ :-] + Eg. Bug-45453, #102, #:53 + +@PARAM1 String, filename of validatable file, excluding path. +@RETURNS String, the koha.atomicupdates.filename if the given file is considered a well formed update script. + Removes the full path if present and returns only the filename component. + +@THROWS Koha::Exception::File, if the given file doesn't have a proper naming convention + +=cut + +sub _validateFilename { + my ($self, $fileName) = @_; + + Koha::Exception::File->throw(error => __PACKAGE__."->_validateFilename():> Filename '$fileName' has unknown suffix") + unless $fileName =~ /\.(sql|perl|pl)$/; #skip other files + + $fileName = File::Basename::basename($fileName); + + return $fileName; +} + +=head _getIssueIdentifier + +Extracts the unique issue identifier from the atomicupdate DB upgrade script. + +@PARAM1 String, filename of validatable file, excluding path. +@RETURNS String, The unique issue identifier + +@THROWS Koha::Exception::Parse, if the unique identifier couldn't be parsed. +=cut + +sub _getIssueIdentifier { + my ($self, $fileName) = @_; + + foreach my $prefix (@allowedIssueIdentifierPrefixes) { + if ($fileName =~ m/$prefix[ -:]*?(\d+)/) { + return "$prefix$1"; + } + } + Koha::Exception::Parse->throw(error => __PACKAGE__."->_getIssueIdentifier($fileName):> couldn't parse the unique issue identifier from filename using allowed prefixes '@allowedIssueIdentifierPrefixes'"); +} + +1; --- a/Koha/AtomicUpdater.pm +++ a/Koha/AtomicUpdater.pm @@ -0,0 +1,223 @@ +package Koha::AtomicUpdater; + +# Copyright Open Source Freedom Fighters +# +# 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 Try::Tiny; +use Data::Format::Pretty::Console qw(format_pretty); + +use Koha::Database; +use Koha::Cache; +use Koha::AtomicUpdate; + +use base qw(Koha::Objects); + +use Koha::Exception::File; +use Koha::Exception::Parse; +use Koha::Exception::BadParameter; + +sub type { + return 'Atomicupdate'; +} + +sub object_class { + return 'Koha::AtomicUpdate'; +} + +sub _get_castable_unique_columns { + return ['atomicupdate_id']; +} + +sub new { + my ($class, $params) = @_; + + my $cache = Koha::Cache->new(); + my $self = $cache->get_from_cache('Koha::AtomicUpdater') || {}; + bless($self, $class); + + $self->{verbose} = $params->{verbose} || $self->{verbose} || 0; + $self->{scriptDir} = $params->{scriptDir} || $self->{scriptDir} || C4::Context->config('intranetdir') . '/installer/data/mysql/atomicupdate/'; + + return $self; +} + +=head getAtomicUpdates + + my $atomicUpdates = $atomicUpdater->getAtomicUpdates(); + +Gets all the AtomicUpdate-objects in the DB. This result should be Koha::Cached. +@RETURNS ARRAYRef of Koha::AtomicUpdate-objects. +=cut + +sub getAtomicUpdates { + my ($self) = @_; + + my @au = $self->search({}); + my %au; #HASHify the AtomicUpdate-objects for easy searching. + foreach my $au (@au) { + $au{$au->filename} = $au; + } + return \%au; +} + +sub addAtomicUpdate { + my ($self, $params) = @_; + print "Adding atomicupdate '".$params->{issue_id}."'\n" if $self->{verbose} > 2; + + my $atomicupdate = Koha::AtomicUpdate->new($params); + $atomicupdate->store(); + $atomicupdate = $self->find({issue_id => $atomicupdate->issue_id}); + return $atomicupdate; +} + +sub removeAtomicUpdate { + my ($self, $issueId) = @_; + print "Deleting atomicupdate '$issueId'\n" if $self->{verbose} > 2; + + my $atomicupdate = $self->find({issue_id => $issueId}); + if ($atomicupdate) { + $atomicupdate->delete; + print "Deleted atomicupdate '$issueId'\n" if $self->{verbose} > 2; + } + else { + Koha::Exception::BadParameter->throw(error => __PACKAGE__."->removeIssueFromLog():> No such Issue '$issueId' stored to the atomicupdates-table"); + } +} + +sub listToConsole { + my ($self) = @_; + my @stringBuilder; + + my @atomicupdates = $self->search({}); + foreach my $au (@atomicupdates) { + push @stringBuilder, $au->unblessed(); + } + return Data::Format::Pretty::Console::format_pretty(\@stringBuilder); +} + +sub listPendingToConsole { + my ($self) = @_; + my @stringBuilder; + + my $atomicUpdates = $self->getPendingAtomicUpdates(); + foreach my $key (sort keys %$atomicUpdates) { + my $au = $atomicUpdates->{$key}; + push @stringBuilder, $au->unblessed(); + } + return Data::Format::Pretty::Console::format_pretty(\@stringBuilder); +} + +sub getPendingAtomicUpdates { + my ($self) = @_; + + my %pendingAtomicUpdates; + my $atomicupdateFiles = $self->_getValidAtomicUpdateScripts(); + my $atomicUpdatesDeployed = $self->getAtomicUpdates(); + foreach my $key (keys(%$atomicupdateFiles)) { + my $au = $atomicupdateFiles->{$key}; + unless ($atomicUpdatesDeployed->{$au->filename}) { + #This script hasn't been deployed. + $pendingAtomicUpdates{$au->filename} = $au; + } + } + return \%pendingAtomicUpdates; +} + +=head applyAtomicUpdates + + my $atomicUpdater = Koha::AtomicUpdater->new(); + my $appliedAtomicupdates = $atomicUpdater->applyAtomicUpdates(); + +Checks the atomicupdates/-directory for any not-applied update scripts and +runs them. + +@RETURNS ARRAYRef of Koha::AtomicUpdate-objects deployed on this run +=cut + +sub applyAtomicUpdates { + my ($self) = @_; + + my %appliedUpdates; + + my $atomicUpdates = $self->getPendingAtomicUpdates(); + foreach my $key ( keys %$atomicUpdates ) { + my $atomicUpdate = $atomicUpdates->{$key}; + my $filename = $atomicUpdate->filename; + print "Looking at file '$filename'\n" if $self->{verbose} > 2; + + if ( $filename =~ /\.sql$/ ) { + my $installer = C4::Installer->new(); + my $rv = $installer->load_sql( $self->{scriptDir}.'/'.$filename ) ? 0 : 1; + } elsif ( $filename =~ /\.(perl|pl)$/ ) { + do $self->{scriptDir}.'/'.$filename; + } + + $atomicUpdate->store(); + $appliedUpdates{$filename} = $atomicUpdate; + print "File '$filename' applied" if $self->{verbose} > 2; + } + + return \%appliedUpdates; +} + +=head _getValidAtomicUpdateScripts + +@RETURNS HASHRef of Koha::AtomicUpdate-objects, of all the files + in the atomicupdates/-directory that can be considered valid. + Validity is currently conforming to the naming convention. + Keys are the filename excluding the full path. + Eg. {'Bug8584-FoundLeBug.pl' => Koha::AtomicUpdate, + ... + } +=cut + +sub _getValidAtomicUpdateScripts { + my ($self) = @_; + + my %atomicUpdates; + opendir( my $dirh, $self->{scriptDir} ); + foreach my $file ( sort readdir $dirh ) { + print "Looking at file $file\n" if $self->{verbose} > 2; + + my $atomicUpdate; + try { + $atomicUpdate = Koha::AtomicUpdate->new({filename => $file}); + } catch { + if (blessed($_)) { + if ($_->isa('Koha::Exception::File')) { + #We can ignore filename validation issues, since the directory has + #loads of other types of files as well. Like README . .. + } + else { + $_->rethrow(); + } + } + else { + die $_; #Rethrow the unknown Exception + } + }; + next unless $atomicUpdate; + + $atomicUpdates{$file} = $atomicUpdate; + } + return \%atomicUpdates; +} + +1; --- a/installer/data/mysql/atomicupdate.pl +++ a/installer/data/mysql/atomicupdate.pl @@ -0,0 +1,96 @@ +#!/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 Getopt::Long; + +use C4::Context; + +use Koha::AtomicUpdater; + +my $verbose = 0; +my $help = 0; +my $apply = 0; +my $remove = ''; +my $insert = ''; +my $list = 0; +my $pending = 0; +my $directory = ''; + +GetOptions( + 'v|verbose:i' => \$verbose, + 'h|help' => \$help, + 'a|apply' => \$apply, + 'd|directory:s' => \$directory, + 'r|remove:s' => \$remove, + 'i|insert:s' => \$insert, + 'l|list' => \$list, + 'p|pending' => \$pending, +); + +my $usage = << 'ENDUSAGE'; + +Runs all the atomicupdate-scripts and sql in the atomicupdates-directory. +Checks from koha.database_updates if the update has already been applied. + +Also acts as a gateway to CRUD the koha.database_updates-table to easily remove +existing upgrade logs. + + -v --verbose Integer, 1 is not so verbose, 3 is maximally verbose. + -h --help Flag, This nice help! + -a --apply Flag, Apply all the pending atomicupdates from the + atomicupdates-directory. + -d --directory Path, From which directory to look for atomicupdate-scripts. + Defaults to '$KOHA_PATH/installer/data/mysql/atomicupdate/' + -r --remove String, Remove the upgrade entry from koha.database_updates + eg. --remove "Bug71337" + -i --insert Path, Add an upgrade log entry for the given atomicupdate-file. + Useful to revert an accidental --remove -operation or for + testing. + eg. -i installer/data/mysql/atomicupdate/Bug5453-Example.pl + -l --list Flag, List all entries in the koha.database_updates-table. + This typically means all applied atomicupdates. + -p --pending Flag, List all pending atomicupdates from the + atomicupdates-directory. + +ENDUSAGE + +if ( $help ) { + print $usage; + exit; +} + +my $atomicupdater = Koha::AtomicUpdater->new({verbose => $verbose}); + +if ($remove) { + $atomicupdater->removeAtomicUpdate($remove); +} +if ($insert) { + $atomicupdater->addAtomicUpdate({filename => $insert}); +} +if ($list) { + print $atomicupdater->listToConsole(); +} +if ($pending) { + print $atomicupdater->listPendingToConsole(); +} +if ($apply) { + $atomicupdater->applyAtomicUpdates(); +} --- a/installer/data/mysql/atomicupdate/Bug14698-AtomicUpdater.pl +++ a/installer/data/mysql/atomicupdate/Bug14698-AtomicUpdater.pl @@ -0,0 +1,36 @@ +#!/usr/bin/perl + +# Copyright Open Source Freedom Fighters +# +# 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 C4::Context; + +my $dbh = C4::Context->dbh(); + +$dbh->do(" +CREATE TABLE `atomicupdates` ( + `atomicupdate_id` int(11) unsigned NOT NULL auto_increment, + `issue_id` varchar(20) NOT NULL, + `filename` varchar(30) NOT NULL, + `modification_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`atomicupdate_id`), + UNIQUE KEY `origincode` (`issue_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; +"); +$dbh->do("INSERT INTO atomicupdates (issue_id, filename) VALUES ('Bug14698', 'Bug14698-AtomicUpdater.pl')"); + +print "Upgrade to Bug 14698 - AtomicUpdater - Keeps track of which updates have been applied to a database done\n"; --- a/installer/data/mysql/kohastructure.sql +++ a/installer/data/mysql/kohastructure.sql @@ -1801,6 +1801,20 @@ CREATE TABLE `printers_profile` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- +-- Table structure for table `atomicupdates` +-- + +DROP TABLE IF EXISTS `atomicupdates`; +CREATE TABLE `atomicupdates` ( + `atomicupdate_id` int(11) unsigned NOT NULL auto_increment, + `issue_id` varchar(20) NOT NULL, + `filename` varchar(128) NOT NULL, + `modification_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`atomicupdate_id`), + UNIQUE KEY `atomic_issue_id` (`issue_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- -- Table structure for table `repeatable_holidays` -- --- a/t/db_dependent/Koha/AtomicUpdater.t +++ a/t/db_dependent/Koha/AtomicUpdater.t @@ -0,0 +1,200 @@ +#!/usr/bin/perl + +# Copyright 2015 Open Source Freedom Fighters +# +# This file is part of Koha. +# +# Koha is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# Koha is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . + +use Modern::Perl; +use Test::More; +use Try::Tiny; +use Encode; + +use t::lib::TestObjects::ObjectFactory; +use t::lib::TestObjects::AtomicUpdateFactory; +use t::lib::TestObjects::FileFactory; +use Koha::AtomicUpdater; + +my $testContext = {}; +my $atomicupdates = t::lib::TestObjects::AtomicUpdateFactory->createTestGroup([ + {issue_id => 'Bug12', + filename => 'Bug12-WatchExMachinaYoullLikeIt.pl'}, + {issue_id => 'Bug14', + filename => 'Bug14-ReturnOfZorro.perl'}, + {issue_id => '#14', + filename => '#14-RobotronInDanger.sql'}, + {issue_id => '#15', + filename => '#15-ILikedPrometheusButAlienWasBetter.pl'}, + ], undef, $testContext); + +subtest "List all deployed atomicupdates" => \&listAtomicUpdates; +sub listAtomicUpdates { + eval { + my $atomicUpdater = Koha::AtomicUpdater->new(); + my $text = $atomicUpdater->listToConsole(); + print $text; + + ok($text =~ m/Bug12-WatchExMachinaYoullLik/, + "Bug12-WatchExMachinaYoullLikeIt"); + ok($text =~ m/Bug14-ReturnOfZorro.perl/, + "Bug14-ReturnOfZorro"); + ok($text =~ m/#14-RobotronInDanger.sql/, + "#14-RobotronInDanger"); + ok($text =~ m/#15-ILikedPrometheusButAli/, + "#15-ILikedPrometheusButAlienWasBetter"); + + }; + if ($@) { + ok(0, $@); + } +} + +subtest "Delete an atomicupdate entry" => \&deleteAtomicupdate; +sub deleteAtomicupdate { + eval { + my $atomicUpdater = Koha::AtomicUpdater->new(); + my $atomicupdate = $atomicUpdater->cast($atomicupdates->{Bug12}->id); + ok($atomicupdate, + "AtomicUpdate '".$atomicupdates->{Bug12}->issue_id."' exists prior to deletion"); + + $atomicUpdater->removeAtomicUpdate($atomicupdate->issue_id); + $atomicupdate = $atomicUpdater->find($atomicupdates->{Bug12}->id); + ok(not($atomicupdate), + "AtomicUpdate '".$atomicupdates->{Bug12}->issue_id."' deleted"); + + }; + if ($@) { + ok(0, $@); + } +} + +subtest "Insert an atomicupdate entry" => \&insertAtomicupdate; +sub insertAtomicupdate { + eval { + my $atomicUpdater = Koha::AtomicUpdater->new(); + my $subtestContext = {}; + my $atomicupdates = t::lib::TestObjects::AtomicUpdateFactory->createTestGroup([ + {issue_id => 'Bug15', + filename => 'Bug15-Inserted.pl'}, + ], undef, $subtestContext, $testContext); + my $atomicupdate = $atomicUpdater->find({issue_id => 'Bug15'}); + ok($atomicupdate, + "Bug15-Inserted.pl"); + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($subtestContext); + + $atomicupdate = $atomicUpdater->find({issue_id => 'Bug15'}); + ok(not($atomicupdate), + "Bug15-Inserted.pl deleted"); + }; + if ($@) { + ok(0, $@); + } +} + +subtest "List pending atomicupdates" => \&listPendingAtomicupdates; +sub listPendingAtomicupdates { + my ($atomicUpdater, $files, $text, $atomicupdates); + my $subtestContext = {}; + eval { + ##Test adding update scripts and deploy them, confirm that no pending scripts detected + $files = t::lib::TestObjects::FileFactory->createTestGroup([ + { filepath => 'atomicupdate/', + filename => '#911-WhoYouGonnaCall.pl', + content => '$ENV{ATOMICUPDATE_TESTS} = 1;',}, + { filepath => 'atomicupdate/', + filename => 'Bug911-WhatchaGonnaDo.pl', + content => '$ENV{ATOMICUPDATE_TESTS}++;',}, + { filepath => 'atomicupdate/', + filename => 'Bug112-LapinlahdenLinnut.pl', + content => '$ENV{ATOMICUPDATE_TESTS}++;',}, + ], + undef, $subtestContext, $testContext); + $atomicUpdater = Koha::AtomicUpdater->new({ + scriptDir => $files->{'#911-WhoYouGonnaCall.pl'}->dirname() + }); + + $text = $atomicUpdater->listPendingToConsole(); + print $text; + + ok($text =~ m/#911-WhoYouGonnaCall.pl/, + "#911-WhoYouGonnaCall is pending"); + ok($text =~ m/Bug911-WhatchaGonnaDo.pl/, + "Bug911-WhatchaGonnaDo is pending"); + ok($text =~ m/Bug112-LapinlahdenLinnut.pl/, + 'Bug112-LapinlahdenLinnut is pending'); + + $atomicupdates = $atomicUpdater->applyAtomicUpdates(); + t::lib::TestObjects::AtomicUpdateFactory->addToContext($atomicupdates, undef, $subtestContext, $testContext); #Keep track of changes + + is($atomicupdates->{'#911-WhoYouGonnaCall.pl'}->issue_id, + '#911', + "#911-WhoYouGonnaCall.pl deployed"); + is($atomicupdates->{'Bug112-LapinlahdenLinnut.pl'}->issue_id, + 'Bug112', + 'Bug112-LapinlahdenLinnut.pl deployed'); + is($atomicupdates->{'Bug911-WhatchaGonnaDo.pl'}->issue_id, + 'Bug911', + "Bug911-WhatchaGonnaDo.pl deployed"); + + ##Test adding scripts to the atomicupdates directory and how we deal with such change. + $files = t::lib::TestObjects::FileFactory->createTestGroup([ + { filepath => 'atomicupdate/', + filename => '#53-PlaceToBe.pl', + content => '$ENV{ATOMICUPDATE_TESTS}++;',}, + { filepath => 'atomicupdate/', + filename => '#54-KohaConInFinlandNextYear.pl', + content => '$ENV{ATOMICUPDATE_TESTS}++;',}, + { filepath => 'atomicupdate/', + filename => '#55-Fiftyfive.pl', + content => '$ENV{ATOMICUPDATE_TESTS}++;',}, + ], + undef, $subtestContext, $testContext); + + $text = $atomicUpdater->listPendingToConsole(); + print $text; + + ok($text =~ m/#53-PlaceToBe.pl/, + "#53-PlaceToBe.pl is pending"); + ok($text =~ m/#54-KohaConInFinlandNextYear.pl/, + "#54-KohaConInFinlandNextYear.pl is pending"); + ok($text =~ m/#55-Fiftyfive.pl/u, + '#55-Fiftyfive.pl'); + + $atomicupdates = $atomicUpdater->applyAtomicUpdates(); + t::lib::TestObjects::AtomicUpdateFactory->addToContext($atomicupdates, undef, $subtestContext, $testContext); #Keep track of changes + + is($atomicupdates->{'#53-PlaceToBe.pl'}->issue_id, + '#53', + "#53-PlaceToBe.pl deployed"); + is($atomicupdates->{'#54-KohaConInFinlandNextYear.pl'}->issue_id, + '#54', + '#54-KohaConInFinlandNextYear.pl deployed'); + is($atomicupdates->{'#55-Fiftyfive.pl'}->issue_id, + '#55', + "#55-Fiftyfive.pl deployed"); + + is($ENV{ATOMICUPDATE_TESTS}, + 6, + "All configured AtomicUpdates deployed"); + }; + if ($@) { + ok(0, $@); + } + t::lib::TestObjects::AtomicUpdateFactory->tearDownTestContext($subtestContext); +} + +t::lib::TestObjects::ObjectFactory->tearDownTestContext($testContext); +done_testing; --- a/t/lib/TestObjects/AtomicUpdateFactory.pm +++ a/t/lib/TestObjects/AtomicUpdateFactory.pm @@ -0,0 +1,105 @@ +package t::lib::TestObjects::AtomicUpdateFactory; + +# 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::AtomicUpdater; +use Koha::Database; + +use Koha::Exception::UnknownProgramState; + +use base qw(t::lib::TestObjects::ObjectFactory); + +sub getDefaultHashKey { + return 'issue_id'; +} +sub getObjectType { + return 'Koha::AtomicUpdate'; +} + +=head t::lib::TestObjects::createTestGroup + + my $atomicupdates = t::lib::TestObjects::AtomicUpdateFactory->createTestGroup([ + {'issue_id' => 'Bug3432', + 'filename' => 'Bug3432-RavingRabbitsMayhem', + 'modification_time' => '2015-01-02 15:59:32', + }, + ], undef, $testContext1, $testContext2, $testContext3); + +Calls Koha::AtomicUpdater to add a Koha::AtomicUpdate object to DB. + +The HASH is keyed with the 'koha.atomicupdates.issue_id', or the given $hashKey. + +There is a duplication check to first look for atomicupdate-rows with the same 'issue_id'. +If a matching atomicupdate is found, then we use the existing Record instead of adding a new one. + +@RETURNS HASHRef of Koha::AtomicUpdate-objects + +See t::lib::TestObjects::ObjectFactory for more documentation +=cut + +sub handleTestObject { + my ($class, $object, $stashes) = @_; + + ##First see if the given Record already exists in the DB. For testing purposes we use the isbn as the UNIQUE identifier. + my $atomicupdate = Koha::AtomicUpdater->find({issue_id => $object->{issue_id}}); + unless ($atomicupdate) { + my $atomicupdater = Koha::AtomicUpdater->new(); + $atomicupdate = $atomicupdater->addAtomicUpdate($object); + } + + Koha::Exception::UnknownProgramState->throw(error => "$class->handleTestObject():> Cannot create a new object\n$@\n") + unless $atomicupdate; + + return $atomicupdate; +} + +=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) = @_; + + $object->{issue_id} = 'BugRancidacid' unless $object->{issue_id}; + $object->{filename} = 'BugRancidacid-LaboratoryExperimentsGoneSour' unless $object->{filename}; + + $self->SUPER::validateAndPopulateDefaultValues($object, $hashKey); +} + +sub deleteTestGroup { + my ($class, $objects) = @_; + + while( my ($key, $object) = each %$objects) { + my $atomicupdate = Koha::AtomicUpdater->cast($object); + eval { + $atomicupdate->delete(); + }; + if ($@) { + warn "$class->deleteTestGroup():> Error hapened: $@\n"; + } + } +} + +1; --- a/t/lib/TestObjects/ObjectFactory.pm +++ a/t/lib/TestObjects/ObjectFactory.pm @@ -166,6 +166,11 @@ sub tearDownTestContext { t::lib::TestObjects::BiblioFactory->deleteTestGroup($stash->{biblio}); delete $stash->{biblio}; } + if ($stash->{atomicupdate}) { + require t::lib::TestObjects::AtomicUpdateFactory; + t::lib::TestObjects::AtomicUpdateFactory->deleteTestGroup($stash->{atomicupdate}); + delete $stash->{atomicupdate}; + } if ($stash->{borrower}) { require t::lib::TestObjects::BorrowerFactory; t::lib::TestObjects::BorrowerFactory->deleteTestGroup($stash->{borrower}); --- a/t/lib/TestObjects/objectFactories.t +++ a/t/lib/TestObjects/objectFactories.t @@ -30,6 +30,8 @@ use t::lib::TestObjects::BorrowerFactory; use Koha::Borrowers; use t::lib::TestObjects::ItemFactory; use Koha::Items; +use t::lib::TestObjects::AtomicUpdateFactory; +use Koha::AtomicUpdater; use t::lib::TestObjects::BiblioFactory; use Koha::Biblios; use t::lib::TestObjects::CheckoutFactory; @@ -472,6 +474,43 @@ sub testLetterTemplateFactory { +########## AtomicUpdateFactory subtests ########## +subtest 't::lib::TestObjects::AtomicUpdateFactory' => \&testAtomicUpdateFactory; +sub testAtomicUpdateFactory { + my ($atomicUpdater, $atomicupdate); + my $subtestContext = {}; + ##Create and Delete using dependencies in the $testContext instantiated in previous subtests. + my $atomicupdates = t::lib::TestObjects::AtomicUpdateFactory->createTestGroup([ + {'issue_id' => 'Bug10', + 'filename' => 'Bug10-RavingRabbitsMayhem', + 'modification_time' => '2015-01-02 15:59:32',}, + {'issue_id' => 'Bug11', + 'filename' => 'Bug11-RancidSausages', + 'modification_time' => '2015-01-02 15:59:33',}, + ], + undef, $subtestContext); + $atomicUpdater = Koha::AtomicUpdater->new(); + $atomicupdate = $atomicUpdater->find({issue_id => $atomicupdates->{Bug10}->issue_id}); + is($atomicupdate->issue_id, + 'Bug10', + "Bug10-RavingRabbitsMayhem created"); + $atomicupdate = $atomicUpdater->find({issue_id => $atomicupdates->{Bug11}->issue_id}); + is($atomicupdate->issue_id, + 'Bug11', + "Bug11-RancidSausages created"); + + t::lib::TestObjects::ObjectFactory->tearDownTestContext($subtestContext); + + $atomicupdate = $atomicUpdater->find({issue_id => $atomicupdates->{Bug10}->issue_id}); + ok(not($atomicupdate), + "Bug10-RavingRabbitsMayhem deleted"); + $atomicupdate = $atomicUpdater->find({issue_id => $atomicupdates->{Bug11}->issue_id}); + ok(not($atomicupdate), + "Bug11-RancidSausages created"); +}; + + + ########## SystemPreferenceFactory subtests ########## subtest 't::lib::TestObjects::SystemPreferenceFactory' => \&testSystemPreferenceFactory; sub testSystemPreferenceFactory { --