The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.
Changes 219
LICENSE 07
MANIFEST 06
META.json 814
META.yml 711
Makefile.PL 20
README 11
lib/WWW/Scraper/ISBN/Driver.pm 0330
lib/WWW/Scraper/ISBN/Record.pm 0174
lib/WWW/Scraper/ISBN.pm 519
t/01base.t 13
t/10object.t 1932
t/11convert.t 038
t/12driver.t 052
t/13record.t 024
t/94metatest.t 19
t/96metatest.t 19
t/lib/WWW/Scraper/ISBN/Test_Driver.pm 16
18 files changed (This is a version diff) 48754
@@ -1,5 +1,22 @@
-Revision history for Perl distribution WWW::Scraper::ISBN
-=========================================================
+Revision history for WWW-Scraper-ISBN
+=====================================
+
+1.02    2014-11-07
+        - fixed license fields in META.json to be lists.
+        - reworked is_valid logic on failure.
+        - extended functional test suite.
+
+1.01    2014-06-12
+        - added tests and checks for blank searches.
+
+1.00    2014-05-27
+        - merged WWW-Scraper-ISBN-Record into this distro.
+        - merged WWW-Scraper-ISBN-Driver into this distro.
+
+0.30    2014-05-19
+        - fixed distribution name in META.
+        - added LICENSE file.
+        - added basic ISBN validation when Business::ISBN is not installed.
 
 0.29    2013-09-03
         - fixed isa/is test.
@@ -0,0 +1,7 @@
+LICENSE for WWW-Scraper-ISBN
+
+Copyright © 2004-2013 Andy Schamp, andy@schamp.net
+Copyright © 2013-2014 Barbie for Miss Barbell Productions.
+
+This distribution is free software; you can redistribute it and/or
+modify it under the Artistic Licence v2.
@@ -1,5 +1,8 @@
 Changes
 lib/WWW/Scraper/ISBN.pm
+lib/WWW/Scraper/ISBN/Driver.pm
+lib/WWW/Scraper/ISBN/Record.pm
+LICENSE
 Makefile.PL
 MANIFEST
 META.json
@@ -7,6 +10,9 @@ META.yml
 README
 t/01base.t
 t/10object.t
+t/11convert.t
+t/12driver.t
+t/13record.t
 t/90podtest.t
 t/91podcover.t
 t/94metatest.t
@@ -1,20 +1,20 @@
 {
     "name": "WWW-Scraper-ISBN",
-    "version": "0.29",
+    "version": "1.02",
     "abstract": "Retrieve information about books from online sources",
     "author": [
       "Andy Schamp <andy@schamp.net>",
       "Barbie <barbie@cpan.org>"
     ],
 
-    "license": "artistic_2",
+    "license": [ "artistic_2" ],
     "dynamic_config" : 0,
     "release_status" : "stable",
     "meta-spec": {
         "version": "2",
         "url": "http://search.cpan.org/dist/CPAN-Meta/lib/CPAN/Meta/Spec.pm"
     },
-    "generated_by": "Hand 1.0",
+    "generated_by": "The Hand of Barbie 1.0",
     "keywords" : [
         "isbn",
         "books"
@@ -24,9 +24,7 @@
         "runtime" : {
             "requires" : {
                 "perl": "5.006",
-                "Carp": "1.00",
-                "WWW::Scraper::ISBN::Driver": "0.20",
-                "WWW::Scraper::ISBN::Record": "0.19"
+                "Carp": "1.00"
             }
         },
         "test" : {
@@ -46,7 +44,15 @@
     "provides": {
         "WWW::Scraper::ISBN": {
             "file": "lib/WWW/Scraper/ISBN.pm",
-            "version": "0.29"
+            "version": "1.02"
+        },
+        "WWW::Scraper::ISBN::Driver": {
+            "file": "lib/WWW/Scraper/ISBN/Driver.pm",
+            "version": "1.02"
+        },
+        "WWW::Scraper::ISBN::Record": {
+            "file": "lib/WWW/Scraper/ISBN/Record.pm",
+            "version": "1.02"
         }
     },
     "no_index": {
@@ -54,7 +60,7 @@
     },
 
     "resources": {
-        "license": "http://www.perlfoundation.org/artistic_license_2_0",
+        "license": [ "http://www.perlfoundation.org/artistic_license_2_0" ],
         "bugtracker": { "web": "http://rt.cpan.org/Public/Dist/Display.html?Name=WWW-Scraper-ISBN" },
         "repository": {
             "url": "git://github.com/barbie/www-scraper-isbn.git",
@@ -1,7 +1,7 @@
 --- #YAML:1.0
-name:                     WWW-Scraper-ISBN
-version:                  0.29
-abstract:                 Retrieve information about books from online sources
+name:       WWW-Scraper-ISBN
+version:    1.02
+abstract:   Retrieve information about books from online sources
 author:
   - Andy Schamp <andy@schamp.net>
   - Barbie <barbie@cpan.org>
@@ -13,8 +13,6 @@ installdirs:              site
 requires:
   perl:                           5.006
   Carp:                           1.00
-  WWW::Scraper::ISBN::Driver:     0.20
-  WWW::Scraper::ISBN::Record:     0.19
 recommends:
   Test::CPAN::Meta:               0
   Test::CPAN::Meta::JSON:         0
@@ -27,7 +25,13 @@ build_requires:
 provides:
   WWW::Scraper::ISBN:
     file:     lib/WWW/Scraper/ISBN.pm
-    version:  0.29
+    version:  1.02
+  WWW::Scraper::ISBN::Driver:
+    file:     lib/WWW/Scraper/ISBN/Driver.pm
+    version:  1.02
+  WWW::Scraper::ISBN::Record:
+    file:     lib/WWW/Scraper/ISBN/Record.pm
+    version:  1.02
 
 no_index:
   directory:
@@ -42,4 +46,4 @@ resources:
 meta-spec:
    version:   1.4
    url:       http://module-build.sourceforge.net/META-spec-v1.4.html
-generated_by: Hand 1.0
+generated_by: The Hand of Barbie 1.0
@@ -16,8 +16,6 @@ WriteMakefile(
 
         # runtime prereqs
 		'Carp'                          => '1.00',
-		'WWW::Scraper::ISBN::Driver'    => '0.20',
-		'WWW::Scraper::ISBN::Record'    => '0.19',
 
         # build/test prereqs
         'IO::File'                      => '0',
@@ -28,7 +28,7 @@ This module requires these other modules and libraries:
 COPYRIGHT AND LICENCE
 
   Copyright (C) 2004-2013 Andy Schamp, andy@schamp.net
-  Copyright (C) 2013      Barbie, barbie@cpan.org
+  Copyright (C) 2013-2014 Barbie, barbie@cpan.org
 
   This distribution is free software; you can redistribute it and/or
   modify it under the Artistic Licence v2.
@@ -0,0 +1,330 @@
+package WWW::Scraper::ISBN::Driver;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+#----------------------------------------------------------------------------
+# Library Modules
+
+use Carp;
+
+#----------------------------------------------------------------------------
+# Public API
+
+# Preloaded methods go here.
+sub new {
+	my $proto = shift;
+	my $class = ref($proto) || $proto;
+
+    my $self = {
+	    FOUND       => 0,
+	    VERBOSITY   => 0,
+	    BOOK        => undef,
+	    ERROR       => ''
+    };
+	
+    bless ($self, $class);
+	return $self;
+}
+
+sub found       { my $self = shift; return $self->_accessor('FOUND',@_)     }
+sub verbosity   { my $self = shift; return $self->_accessor('VERBOSITY',@_) }
+sub book        { my $self = shift; return $self->_accessor('BOOK',@_)      }
+sub error       { my $self = shift; return $self->_accessor('ERROR',@_)     }
+
+sub _accessor {
+	my $self     = shift;
+	my $accessor = shift;
+	if (@_) { $self->{$accessor} = shift };
+	return $self->{$accessor};
+}
+
+sub search {
+	croak(q{Child class must overload 'search()' method.});
+}
+
+#----------------------------------------------------------------------------
+# Internal Class methods
+
+# a generic method for storing the error & setting not found
+sub handler {
+	my $self = shift;
+	if (@_) {
+		$self->{ERROR} = shift;
+		print "Error: $self->{ERROR}\n"	if $self->verbosity;
+	};
+	return $self->found(0);
+}
+
+sub convert_to_ean13 {
+	my $self = shift;
+    my $isbn = shift || return;
+    my $prefix;
+
+    return  unless(length $isbn == 10 || length $isbn == 13);
+
+    if(length $isbn == 13) {
+        return  if($isbn !~ /^(978|979)(\d{10})$/);
+        ($prefix,$isbn) = ($1,$2);
+    } else {
+        return  if($isbn !~ /^(\d{10}|\d{9}X)$/);
+        $prefix = '978';
+    }
+
+    my $isbn13 = $prefix . $isbn;
+    chop($isbn13);
+    my @isbn = split(//,$isbn13);
+    my ($lsum,$hsum) = (0,0);
+    while(@isbn) {
+        $hsum += shift @isbn;
+        $lsum += shift @isbn;
+    }
+
+    my $csum = ($lsum * 3) + $hsum;
+    $csum %= 10;
+    $csum = 10 - $csum  if($csum != 0);
+
+    return $isbn13 . $csum;
+}
+
+sub convert_to_isbn10 {
+	my $self = shift;
+    my $ean  = shift || return;
+    my ($isbn,$isbn10);
+
+    return  unless(length $ean == 10 || length $ean == 13);
+
+    if(length $ean == 13) {
+        return  if($ean !~ /^(?:978|979)(\d{9})\d$/);
+        ($isbn,$isbn10) = ($1,$1);
+    } else {
+        return  if($ean !~ /^(\d{9})[\dX]$/);
+        ($isbn,$isbn10) = ($1,$1);
+    }
+
+	my ($csum, $pos, $digit) = (0, 0, 0);
+    for ($pos = 9; $pos > 0; $pos--) {
+        $digit = $isbn % 10;
+        $isbn /= 10;             # Decimal shift ISBN for next time 
+        $csum += ($pos * $digit);
+    }
+    $csum %= 11;
+    $csum = 'X'   if ($csum == 10);
+    return $isbn10 . $csum;
+}
+
+sub is_valid {
+	my $self = shift;
+    my $isbn = shift or return 0;
+
+    # validate and convert into EAN13 format
+    my $ean = $self->convert_to_ean13($isbn);
+
+    return 0 if(!$ean);
+    return 0 if(length $isbn == 13 && $isbn ne $ean);
+    return 0 if(length $isbn == 10 && $isbn ne $self->convert_to_isbn10($ean));
+
+    return 1;
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+WWW::Scraper::ISBN::Driver - Driver class for WWW::Scraper::ISBN module.
+
+=head1 SYNOPSIS
+
+    use WWW::Scraper::ISBN::Driver;
+    
+    $driver = WWW::Scraper::ISBN::Driver->new();
+    $driver->search($isbn);
+
+    if ($driver->found) { ... }
+    $driver->verbosity(1);
+    
+    my $book = $driver->book();
+    print $book('title');
+    print $driver->error;
+
+=head1 REQUIRES
+
+Requires the following modules be installed:
+
+    Carp
+
+=head1 DESCRIPTION
+
+This is a base class, from which all site-specific drivers should inherit its 
+members and methods.  Driver subclasses named 'C<$name>' should be packaged as 
+C<WWW::Scraper::ISBN::$name_Driver>, e.g. C<WWW::Scraper::ISBN::LOC_Driver> 
+for the LOC (Library of Congress) driver. Each driver need only implement the 
+C<search()> method, though they may have as many other methods as they need to 
+get their job done. Only C<search()> will be called by 
+C<< WWW::Scraper::ISBN->search() >>.
+
+=head2 Standard Fields
+
+It is important that the different drivers return at least a core set of 
+information, though they may return additional information.  The following 
+self-explanatory fields should exist in C<< $driver->book >>:
+
+=over 4
+
+=item author
+
+=item title
+
+=item isbn
+
+=back
+
+In some cases, there may be no information for these fields, and so these may 
+be set to the empty string. However, they must still be set in the hash! 
+
+Additional standard fields may be added in the future. 'pages', 'weight', 
+'height', 'depth and 'description' are common. 
+
+=head2 Expiration
+
+Due to the dynamic, ever-changing nature of the web, it is highly likely that 
+the site from which many of these drivers glean their information will change.  
+Hopefully, driver maintainers will keep drivers up to date, but they will all 
+expire, and may behave unexpectedly.  Keep this in mind if the driver 
+continually returns weird results.
+
+=head1 METHODS
+
+The following methods are provided by C<WWW::Scraper::ISBN::Driver>:
+
+=over 4
+
+=item C<new()>
+
+    $drv = WWW::Scraper::ISBN::Driver->new()
+
+Class constructor. Creates new driver object and returns a reference to it. 
+Sets the following default values:
+
+    found = 0;
+    verbosity = 0;
+    book = undef;
+    error = '';
+
+=item C<found() or found($bool)>
+
+    if ($drv->found) { # ... }
+    $drv->found(1);
+
+Accessor/Mutator method for handling the search status of this record. This is 
+0 by default, and should only be set true if search was deemed successful and 
+C<< $driver->book >> contains appropriate information.
+
+=item C<verbosity() or verbosity($level)>
+
+    $driver->verbosity(3);
+    if ($driver->verbosity == 2) { print 'blah blah blah'; }
+
+Accessor/Mutator method for handling the verbosity level to be generated by 
+this driver as it is going. This can be used to print useful information by 
+the driver as it is running.
+
+=item C<book() or book($hashref)>
+
+    my $book = $drv->book;
+    print $book->{'title'}; 
+    print $book->{'author'};
+    $another_book = { 'title' => 'Some book title',
+        'author' => "Author of some book"
+    };
+    $drv->book( $another_book );
+
+Accessor/Mutator method for handling the book information retrieved by the 
+driver. The driver should create an anonymous hash containing the standard 
+fields. C<< WWW::Scraper::ISBN->search >> sets the 
+C<< WWW::Scraper::ISBN::Record->book() >> field to this value.
+
+=item C<error() or error($error_string)>
+
+    print $driver->error;
+    $driver->error('Invalid ISBN number, or some similar error.');
+
+Accessor/Mutator method for handling any errors which occur during the search.
+The search drivers may add errors to record fields, which may be useful in 
+gleaning information about failed searches.
+
+=item C<search($isbn)>
+
+    my $record = $driver->search('123456789X');
+
+Searches for information on the given ISBN number. Each driver must define its
+own search routine, doing whatever is necessary to retrieve the desired 
+information. If found, it should set C<< $driver->found >> and 
+C<< $driver->book >> accordingly.
+
+=item C<handler() or handler($error_string)>
+
+    $driver->handler('Invalid ISBN number, or some similar error.');
+
+A generic handler method for handling errors.  If given an error string, will 
+store as per C<< $self->error($error_string) >> and print on the standard 
+output if verbosity is set.  Returns C<< $self->found(0) >>.
+
+=item C<convert_to_ean13($isbn)>
+
+Given a 10/13 character ISBN, this function will return the correct 13 digit
+ISBN, also known as EAN13.
+
+=item C<convert_to_isbn10($isbn)>
+
+Given a 10/13 character ISBN, this function will return the correct 10 digit 
+ISBN.
+
+=item C<is_valid($isbn)>
+
+Given a 10/13 character ISBN, this function will return 1 if it considers it
+looks like a valid ISBN, otherwise returns 0.
+
+=back
+
+=head1 KNOWN DRIVERS
+
+The current list of known drivers can be installed via the following Bundle:
+
+=over 4
+
+L<Bundle::WWW::Scraper::ISBN::Drivers>
+
+=back
+
+If you create a driver, please post a GitHub pull request or create an RT 
+ticket against the Bundle distribution.
+
+=head1 SEE ALSO
+
+=over 4
+
+L<WWW::Scraper::ISBN>
+
+L<WWW::Scraper::ISBN::Record>
+
+=back
+
+=head1 AUTHOR
+
+  2004-2013 Andy Schamp, E<lt>andy@schamp.netE<gt>
+  2013-2014 Barbie, E<lt>barbie@cpan.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+  Copyright 2004-2013 by Andy Schamp
+  Copyright 2013-2014 by Barbie
+
+  This distribution is free software; you can redistribute it and/or
+  modify it under the Artistic Licence v2.
+
+=cut
@@ -0,0 +1,174 @@
+package WWW::Scraper::ISBN::Record;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.02';
+
+#----------------------------------------------------------------------------
+# Public API
+
+sub new {
+	my $proto = shift;
+	my $class = ref($proto) || $proto;
+	my $self = {
+        ISBN        => undef,
+        FOUND       => 0,
+        FOUND_IN    => undef,
+        BOOK        => undef,
+        ERROR       => '',
+    };
+
+	bless ($self, $class);
+	return $self;
+}
+
+sub isbn        { my $self = shift; return $self->_accessor('ISBN',@_)     }
+sub found       { my $self = shift; return $self->_accessor('FOUND',@_)    }
+sub found_in    { my $self = shift; return $self->_accessor('FOUND_IN',@_) }
+sub book        { my $self = shift; return $self->_accessor('BOOK',@_)     }
+sub error       { my $self = shift; return $self->_accessor('ERROR',@_)    }
+
+sub _accessor {
+	my $self     = shift;
+	my $accessor = shift;
+	if (@_) { $self->{$accessor} = shift };
+	return $self->{$accessor};
+}
+
+1;
+
+__END__
+
+# Documentation
+
+=head1 NAME
+
+WWW::Scraper::ISBN::Record - Book Record class for L<WWW::Scraper::ISBN> module.
+
+=head1 SYNOPSIS
+
+used from within WWW::Scraper::ISBN.  No need to invoke directly.  But if you want to:
+
+    use WWW::Scraper::ISBN::Record;
+    $record = WWW::Scraper::ISBN::Record->new();
+
+It is usually best to let an instantiation of WWW::Scraper::ISBN create it and
+search for it. This class does not know how to search on its own.
+
+    print $record->isbn;
+
+    if ($record->found) {
+	    print $record->found_in;
+    } else {
+	    print "not found";
+    }
+
+    $book = $record->book;
+    print $book->{'title'};
+    print $book->{'author'};
+    # etc.
+
+    if ($record->error) { print $record->error(); }
+
+=head1 DESCRIPTION
+
+The WWW::Scraper::ISBN::Record module defines a class that can be used to deal 
+with book information.  It was primarily created as a return type for the 
+L<WWW::Scraper::ISBN> module, though it could be used for other purposes.  It 
+knows minimal information about itself, whether the book was found, where it 
+was found, its ISBN number, and whether any errors occurred.  It is usually up 
+to the L<WWW::Scraper::ISBN::Driver> and its subclasses to make sure that the 
+fields get set correctly.
+
+=head1 METHODS
+
+=over 4
+
+=item C<new()>
+
+Class Constructor.  Usually invoked by C<< WWW::Scraper::ISBN->search() >>.  
+Takes no parameters, creates an object with the default values:
+
+    isbn = undef;
+    found = 0;
+    found_in = undef;
+    book = undef;
+    error = "";
+
+=item C<isbn() or isbn($isbn_number)>
+
+    print $record->isbn; # returns the ISBN number string
+    $record->isbn("123456789X"); # set the ISBN 
+
+Accessor/Mutator method for handling the ISBN associated with this record.  
+
+=item C<found() or found($bool)>
+
+    if ($record->found) { # ... }
+    $record->found(1);
+
+Accessor/Mutator method for handling the search status of this record.  This is
+0 by default, and should only be set to true if the Record object contains the
+desired information, as retrieved by C<< WWW::Scraper::ISBN::Record->book() >>.
+
+=item C<found_in() or found_in($DRIVER_NAME)>
+
+    print $record->found_in;
+    $record->found_in("Driver_name");
+
+Accessor/Mutator method for handling the L<WWW::Scraper::ISBN::Driver> subclass
+that first successfully retrieved the desired record.  Please note that this 
+may depend upon the order in which the drivers are invoked, as set by 
+C<< WWW::Scraper::ISBN->drivers() >>.  Returns the driver name of the successful 
+driver, e.g. "LOC" if found by C<< WWW::Scraper::ISBN::LOC_Driver->search() >>.
+
+=item C<book() or book($hashref)>
+
+    my $book = $record->book;
+    print $book->{'title'};
+    print $book->{'author'};
+    $another_book = { 
+       'title'  => "Some book title",
+       'author' => "Author of some book"
+    }; 
+    $record->book( $another_book );
+
+Accessor/Mutator method for handling the book information retrieved by the driver.  Set to a hashref by the driver, returns 
+a hashref when invoked alone.  The resulting hash should contain the standard fields as specified by 
+L<WWW::Scraper::ISBN::Driver>, and possibly additional fields based on the driver used.
+
+=item C<error() or error($error_string)>
+
+    print $record->error;
+    $record->error("Invalid ISBN number, or some similar error.");
+
+Accessor/Mutator method for handling any errors which occur during the search.  The search drivers may add errors to record 
+fields, which may be useful in gleaning information about failed searches.
+
+=back
+
+=head1 SEE ALSO
+
+=over 4
+
+=item L<WWW::Scraper::ISBN>
+
+=item L<WWW::Scraper::ISBN::Driver>
+
+=back
+
+=head1 AUTHOR
+
+  2004-2013 Andy Schamp, E<lt>andy@schamp.netE<gt>
+  2013-2014 Barbie, E<lt>barbie@cpan.orgE<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+  Copyright 2004-2013 by Andy Schamp
+  Copyright 2013-2014 by Barbie
+
+  This distribution is free software; you can redistribute it and/or
+  modify it under the Artistic Licence v2.
+
+=cut
@@ -3,14 +3,21 @@ package WWW::Scraper::ISBN;
 use strict;
 use warnings;
 
+our $VERSION = '1.02';
+
+#----------------------------------------------------------------------------
+# Library Modules
+
 use Carp;
 use WWW::Scraper::ISBN::Record;
-
-our $VERSION = '0.29';
+use WWW::Scraper::ISBN::Driver;
 
 eval "use Business::ISBN";
 my $business_isbn_loaded = ! $@;
 
+#----------------------------------------------------------------------------
+# Public API
+
 # Preloaded methods go here.
 sub new {
     my $proto = shift;
@@ -42,9 +49,16 @@ sub reset_drivers {
 sub search {
     my ($self,$isbn) = @_;
 
+    croak("Invalid ISBN specified [].\n") unless($isbn);        
+
     if($business_isbn_loaded) {
+        # Business::ISBN has strong validation algorithms
         my $isbn_object = Business::ISBN->new($isbn);
-        croak("Invalid ISBN specified.\n") unless($isbn_object && $isbn_object->is_valid);
+        croak("Invalid ISBN specified [$isbn].\n") unless($isbn_object && $isbn_object->is_valid);
+    } else {
+        # our fallback just validates it looks like an ISBN
+        my $isbn_object = WWW::Scraper::ISBN::Driver->new();
+        croak("Invalid ISBN specified [$isbn].\n") unless($isbn_object && $isbn_object->is_valid($isbn));        
     }
 
     croak("No search drivers specified.\n")
@@ -209,12 +223,12 @@ the given isbn.
 =head1 AUTHOR
 
   2004-2013 Andy Schamp, E<lt>andy@schamp.netE<gt>
-  2013      Barbie, E<lt>barbie@cpan.orgE<gt>
+  2013-2014 Barbie, E<lt>barbie@cpan.orgE<gt>
 
 =head1 COPYRIGHT AND LICENSE
 
   Copyright 2004-2013 by Andy Schamp
-  Copyright 2013 by Barbie
+  Copyright 2013-2014 by Barbie
 
   This distribution is free software; you can redistribute it and/or
   modify it under the Artistic Licence v2.
@@ -1,8 +1,10 @@
 #!/usr/bin/perl -w
 use strict;
 
-use Test::More tests => 1;
+use Test::More tests => 3;
 
 BEGIN {
 	use_ok( 'WWW::Scraper::ISBN' );
+	use_ok( 'WWW::Scraper::ISBN::Driver' );
+	use_ok( 'WWW::Scraper::ISBN::Record' );
 }
@@ -21,48 +21,61 @@ is($drivers[0],'Test');
 @drivers = $scraper->reset_drivers();
 is(@drivers,0);
 
-# Can we search for a vslid ISBN, with no driver?
+# Can we search for a valid ISBN, with no driver?
 
-my $isbn = "123456789X";
+my $isbn = '9780571239566';
 my $record;
 eval { $record = $scraper->search($isbn) };
 like($@,qr/No search drivers specified/);
 
+# Can we search for a valid ISBN, with driver?
+
 @drivers = $scraper->drivers("Test");
 is(@drivers,1);
 is($drivers[0],'Test');
 
-# Can we search for a vslid ISBN, with driver?
-
 eval { $record = $scraper->search($isbn) };
 is($@,'');
 isa_ok($record,'WWW::Scraper::ISBN::Record');
 is($record->found,1);
 my $b = $record->book;
-is($b->{isbn},'123456789X');
+is($b->{isbn},'9780571239566');
 is($b->{title},'test title');
 is($b->{author},'test author');
 
-# Can we search for an invalid ISBN?
+# Can we search for a valid ISBN, but not found?
+
+$isbn = '9780987654328';
+eval { $record = $scraper->search($isbn) };
+is($@,'');
+isa_ok($record,'WWW::Scraper::ISBN::Record');
+is($record->found,0);
+is($record->book,undef);
+is($record->error,'');
 
-eval "use Business::ISBN";
-my $business_isbn_loaded = ! $@;
+# Can we handle errors?
+
+$isbn = '9790571239589';
+eval { $record = $scraper->search($isbn) };
+is($@,'');
+isa_ok($record,'WWW::Scraper::ISBN::Record');
+is($record->found,0);
+is($record->book,undef);
+is($record->error,'Website unavailable');
+
+# Can we search for a blank ISBN?
+eval { $record = $scraper->search(); };
+like($@,qr/Invalid ISBN specified/);
+
+# Can we search for an invalid ISBN?
 
-$isbn = "1234567890";
+$isbn = '098765432X';
 $record = undef;
 eval { $record = $scraper->search($isbn) };
 
 # Note: validation is different if Business::ISBN is installed
 
-if($business_isbn_loaded) {
-    like($@,qr/Invalid ISBN specified/);
-    is($record,undef);
-} else {
-    is($@,'');
-    isa_ok($record,'WWW::Scraper::ISBN::Record');
-    is($record->found,0);
-    $b = $record->book;
-    is($b,undef);
-}
+like($@,qr/Invalid ISBN specified/);
+is($record,undef);
 
 done_testing();
@@ -0,0 +1,38 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Test::More tests => 26;
+use WWW::Scraper::ISBN::Driver;
+
+###########################################################
+
+my %isbn = (
+    '098765432X'    => { ean13 => '9780987654328',  isbn10 => '0987654322' },
+    '0987654322'    => { ean13 => '9780987654328',  isbn10 => '0987654322' },
+    '0987654321'    => { ean13 => '9780987654328',  isbn10 => '0987654322' },
+    '0571239560'    => { ean13 => '9780571239566',  isbn10 => '0571239560' },
+    
+    '9780571239566' => { ean13 => '9780571239566',  isbn10 => '0571239560' },
+    '9780571239567' => { ean13 => '9780571239566',  isbn10 => '0571239560' },
+    '9780571239580' => { ean13 => '9780571239580',  isbn10 => '0571239587' },
+
+    '9790571239589' => { ean13 => '9790571239589',  isbn10 => '0571239587' },
+    '9790577229560' => { ean13 => '9790577229560',  isbn10 => '0577229567' },
+
+    '9790579239567' => { ean13 => '9790579239567',  isbn10 => '057923956X' },
+    
+    '978057123956'  => { ean13 => undef,            isbn10 => undef },
+    '9990571239567' => { ean13 => undef,            isbn10 => undef },
+    '098765432Z'    => { ean13 => undef,            isbn10 => undef },
+);
+
+###########################################################
+# Internal tests
+
+my $driver = WWW::Scraper::ISBN::Driver->new();
+for my $isbn (keys %isbn) {
+    is($driver->convert_to_ean13($isbn), $isbn{$isbn}{ean13} ,".. isbn 13 convert for $isbn");
+    is($driver->convert_to_isbn10($isbn),$isbn{$isbn}{isbn10},".. isbn 10 convert for $isbn");
+}
+
+###########################################################
@@ -0,0 +1,52 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Test::More tests => 27;
+
+use WWW::Scraper::ISBN::Driver;
+
+my $driver = WWW::Scraper::ISBN::Driver->new();
+isa_ok($driver,'WWW::Scraper::ISBN::Driver');
+my $driver2 = $driver->new();
+isa_ok($driver2,'WWW::Scraper::ISBN::Driver');
+
+my %defaults = (
+    found       => 0,
+    verbosity   => 0,
+    book        => undef,
+    error       => ''
+);
+
+for my $method (qw( found verbosity book error )) {
+    is($driver->$method(),$defaults{$method},".. default test for $method");
+    is($driver->$method('value'),'value',".. value test for $method");
+}
+
+eval { $driver->search() };
+like($@,qr/Child class/);
+
+$driver->found(1);
+is($driver->found,1);
+is($driver->handler('this is an error'),0);
+is($driver->found,0);
+is($driver->error,'this is an error');
+is($driver->handler(),0);
+is($driver->error,'this is an error'); # stays the same, if no other error given
+
+# now with verbose off
+
+$driver->verbosity(0);
+
+eval { $driver->search() };
+like($@,qr/Child class/);
+
+is($driver->handler('this is still an error'),0);
+is($driver->found,0);
+is($driver->error,'this is still an error');
+
+is($driver->is_valid(),0);
+is($driver->is_valid('098765432X'),0);
+is($driver->is_valid('9990571239567'),0);
+is($driver->is_valid('9780571239567'),0);
+is($driver->is_valid('0987654322'),1);
+is($driver->is_valid('9780571239566'),1);
@@ -0,0 +1,24 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Test::More tests => 12;
+
+use WWW::Scraper::ISBN::Record;
+
+my $record = WWW::Scraper::ISBN::Record->new();
+isa_ok($record,'WWW::Scraper::ISBN::Record');
+my $record2 = $record->new();
+isa_ok($record2,'WWW::Scraper::ISBN::Record');
+
+my %defaults = (
+    isbn        => undef,
+    found       => 0,
+    found_in    => undef,
+    book        => undef,
+    error       => '',
+);
+
+for my $method (qw( isbn found found_in book error )) {
+    is($record->$method(),$defaults{$method},".. default test for $method");
+    is($record->$method('value'),'value',".. value test for $method");
+}
@@ -23,6 +23,14 @@ is($meta->{version},$version,
 if($meta->{provides}) {
     for my $mod (keys %{$meta->{provides}}) {
         is($meta->{provides}{$mod}{version},$version,
-            "META.yml entry [$mod] version matches");
+            "META.yml entry [$mod] version matches distribution version");
+
+        eval "require $mod";
+        my $VERSION = '$' . $mod . '::VERSION';
+        my $v = eval "$VERSION";
+        is($meta->{provides}{$mod}{version},$v,
+            "META.json entry [$mod] version matches module version");
+
+        isnt($meta->{provides}{$mod}{version},0);
     }
 }
@@ -23,6 +23,14 @@ is($meta->{version},$version,
 if($meta->{provides}) {
     for my $mod (keys %{$meta->{provides}}) {
         is($meta->{provides}{$mod}{version},$version,
-            "META.json entry [$mod] version matches");
+            "META.json entry [$mod] version matches distribution version");
+
+        eval "require $mod";
+        my $VERSION = '$' . $mod . '::VERSION';
+        my $v = eval "$VERSION";
+        is($meta->{provides}{$mod}{version},$v,
+            "META.json entry [$mod] version matches module version");
+
+        isnt($meta->{provides}{$mod}{version},0);
     }
 }
@@ -6,12 +6,17 @@ sub search {
     my $self = shift;
     my $isbn = shift;
 
-    if($isbn ne '123456789X') {
+    if($isbn eq '9780987654328') {
         $self->found(0);
         $self->book(undef);
         return;
     }
 
+    if($isbn eq '9790571239589') {
+        $self->handler('Website unavailable');
+        return;
+    }
+
     my $bk = {
         isbn    => $isbn,
         title   => 'test title',