The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
Changes 214
LICENSE 07
MANIFEST 06
META.json 511
META.yml 610
Makefile.PL 31
README 11
lib/WWW/Scraper/ISBN/Driver.pm 0329
lib/WWW/Scraper/ISBN/Record.pm 0174
lib/WWW/Scraper/ISBN.pm 519
t/01base.t 13
t/10object.t 158
t/11convert.t 038
t/12driver.t 045
t/13record.t 024
t/94metatest.t 19
t/96metatest.t 19
17 files changed (This is a version diff) 40708
@@ -1,5 +1,17 @@
-Revision history for Perl distribution WWW::Scraper::ISBN
-=========================================================
+Revision history for WWW-Scraper-ISBN
+=====================================
+
+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,6 +1,6 @@
 {
     "name": "WWW-Scraper-ISBN",
-    "version": "0.29",
+    "version": "1.01",
     "abstract": "Retrieve information about books from online sources",
     "author": [
       "Andy Schamp <andy@schamp.net>",
@@ -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.01"
+        },
+        "WWW::Scraper::ISBN::Driver": {
+            "file": "lib/WWW/Scraper/ISBN/Driver.pm",
+            "version": "1.01"
+        },
+        "WWW::Scraper::ISBN::Record": {
+            "file": "lib/WWW/Scraper/ISBN/Record.pm",
+            "version": "1.01"
         }
     },
     "no_index": {
@@ -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.01
+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.01
+  WWW::Scraper::ISBN::Driver:
+    file:     lib/WWW/Scraper/ISBN/Driver.pm
+    version:  1.01
+  WWW::Scraper::ISBN::Record:
+    file:     lib/WWW/Scraper/ISBN/Record.pm
+    version:  1.01
 
 no_index:
   directory:
@@ -8,7 +8,7 @@ use ExtUtils::MakeMaker;
 
 WriteMakefile(
     AUTHOR              => 'Barbie <barbie@cpan.org>',
-    NAME                => 'WWW::Scraper::ISBN',
+    NAME                => 'WWW-Scraper-ISBN',
     VERSION_FROM        => 'lib/WWW/Scraper/ISBN.pm',
     ABSTRACT            => 'Retrieve information about books from online sources',
     NO_META             => 1,
@@ -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,329 @@
+package WWW::Scraper::ISBN::Driver;
+
+use strict;
+use warnings;
+
+our $VERSION = '1.01';
+
+#----------------------------------------------------------------------------
+# 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 || (length $isbn == 13 && $isbn ne $ean)
+                 || (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.01';
+
+#----------------------------------------------------------------------------
+# 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.01';
+
+#----------------------------------------------------------------------------
+# 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,7 +21,7 @@ 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 $record;
@@ -32,7 +32,7 @@ like($@,qr/No search drivers specified/);
 is(@drivers,1);
 is($drivers[0],'Test');
 
-# Can we search for a vslid ISBN, with driver?
+# Can we search for a valid ISBN, with driver?
 
 eval { $record = $scraper->search($isbn) };
 is($@,'');
@@ -43,10 +43,11 @@ is($b->{isbn},'123456789X');
 is($b->{title},'test title');
 is($b->{author},'test author');
 
-# Can we search for an invalid ISBN?
+# Can we search for a blank ISBN?
+eval { $record = $scraper->search(); };
+like($@,qr/Invalid ISBN specified/);
 
-eval "use Business::ISBN";
-my $business_isbn_loaded = ! $@;
+# Can we search for an invalid ISBN?
 
 $isbn = "1234567890";
 $record = undef;
@@ -54,15 +55,7 @@ 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,45 @@
+#!/usr/bin/perl -w
+use strict;
+
+use Test::More tests => 21;
+
+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');
@@ -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);
     }
 }