From 3ca733caedbd006db988423fac135aecd81ba5d1 Mon Sep 17 00:00:00 2001 From: dakkar Date: Tue, 18 Oct 2016 19:14:08 +0100 Subject: datastore has and passes tests --- lib/AniDB/Datastore.pm | 59 ++++++++--------- t/tests/datastore.t | 174 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+), 29 deletions(-) create mode 100644 t/tests/datastore.t diff --git a/lib/AniDB/Datastore.pm b/lib/AniDB/Datastore.pm index 41602e6..d5f61b6 100644 --- a/lib/AniDB/Datastore.pm +++ b/lib/AniDB/Datastore.pm @@ -1,18 +1,17 @@ package AniDB::Datastore; use 5.024; -use experimental 'signatures'; use Moo; -use Path::Tiny; +use experimental 'signatures'; use Try::Tiny; use JSON::MaybeXS qw(encode_json decode_json); use namespace::clean; has database => ( is => 'ro', required => 1 ); -has dbh => ( is => 'ro', lazy => 1 ); +has dbh => ( is => 'lazy' ); sub _build_dbh($self) { require DBI; - return DBI->connect( + my $dbh = DBI->connect( 'dbi:SQLite:dbname='.$self->database, '', '', { @@ -21,20 +20,20 @@ sub _build_dbh($self) { PrintError => 0, }, ); -} -sub BUILD($self) { try { - $self->dbh->selectall_arrayref('SELECT * FROM path_info LIMIT 1'); + $dbh->selectall_arrayref('SELECT * FROM path_info LIMIT 1'); } catch { if (/\bno such table\b/) { my @sql = do { local $/ = ";\n\n"; }; - $self->dbh->do($_) for @sql; + $dbh->do($_) for @sql; } else { die $_; } }; + + return $dbh; } sub has_changed($self,$path,$stat) { @@ -42,7 +41,7 @@ sub has_changed($self,$path,$stat) { # if we don't know about the file, it's definitely changed! return 1 unless $path_info; - return ( + return not( $path_info->{size} == $stat->size && $path_info->{mtime} == $stat->mtime ); @@ -51,7 +50,8 @@ sub has_changed($self,$path,$stat) { sub rename($self,$path,$new_path) { $self->dbh->do( q{UPDATE path_info SET name=? WHERE name=?}, - $path->stringify,$new_path->stringify, + {}, + $new_path->stringify,$path->stringify, ); } @@ -72,21 +72,22 @@ sub full_info_for($self,$path) { sub path_info_for($self,$path) { my $ret = $self->dbh->selectall_arrayref( - qr{SELECT * FROM path_info WHERE name=?}, + q{SELECT * FROM path_info WHERE name=?}, { Slice => {} }, $path->stringify, ); + return unless $ret && $ret->[0]; return $ret->[0]; } sub update_path_info($self,$path,$path_info) { my @binds = (@{$path_info}{qw(size mtime hash)}, $path->stringify); - return $self->dbh->do( - qr{UPDATE path_info SET size=?, mtime=?, hash=? WHERE name=?}, + return 0+$self->dbh->do( + q{UPDATE path_info SET size=?, mtime=?, hash=? WHERE name=?}, {}, @binds, - ) or $self->dbh->do( - qr{INSERT INTO path_info(size,mtime,hash,name) VALUES (?,?,?,?)}, + ) || 0+$self->dbh->do( + q{INSERT INTO path_info(size,mtime,hash,name) VALUES (?,?,?,?)}, {}, @binds, ); @@ -94,7 +95,7 @@ sub update_path_info($self,$path,$path_info) { sub episode_info_for($self,$args) { my $ret = $self->dbh->selectall_arrayref( - qr{SELECT json FROM episode_info WHERE hash=? AND size=?}, + q{SELECT json FROM episode_info WHERE hash=? AND size=?}, { }, @{$args}{qw(hash size)}, ); @@ -105,22 +106,22 @@ sub episode_info_for($self,$args) { sub update_episode_info($self,$path_info, $episode_info) { my $json = encode_json($episode_info); my @binds = ($json, @{$path_info}{qw(size hash)}); - return $self->dbh->do( - qr{UPDATE episode_info SET json=? WHERE size=? AND hash=?}, + return 0+$self->dbh->do( + q{UPDATE episode_info SET json=? WHERE size=? AND hash=?}, {}, @binds, - ) or $self->dbh->do( - qr{INSERT INTO episode_info(json,size,hash) VALUES (?,?,?)}, + ) || 0+$self->dbh->do( + q{INSERT INTO episode_info(json,size,hash) VALUES (?,?,?)}, {}, @binds, ); } -sub anime_info_for($self,$args) { +sub anime_info_for($self,$episode_info) { my $ret = $self->dbh->selectall_arrayref( - qr{SELECT json FROM anime_info WHERE aid=?}, + q{SELECT json FROM anime_info WHERE aid=?}, { }, - @{$args}{qw(aid)}, + @{$episode_info}{qw(aid)}, ); return unless $ret && $ret->[0]; return decode_json($ret->[0][0]); @@ -128,13 +129,13 @@ sub anime_info_for($self,$args) { sub update_anime_info($self,$episode_info, $anime_info) { my $json = encode_json($anime_info); - my @binds = ($json, $episode_info->{eid}); - return $self->dbh->do( - qr{UPDATE anime_info SET json=? WHERE eid=?}, + my @binds = ($json, $episode_info->{aid}); + return 0+$self->dbh->do( + q{UPDATE anime_info SET json=? WHERE aid=?}, {}, @binds, - ) or $self->dbh->do( - qr{INSERT INTO anime_info(json,eid) VALUES (?,?)}, + ) || 0+$self->dbh->do( + q{INSERT INTO anime_info(json,aid) VALUES (?,?)}, {}, @binds, ); @@ -158,6 +159,6 @@ CREATE TABLE episode_info ( ); CREATE TABLE anime_info ( - eid INT PRIMARY KEY, + aid INT PRIMARY KEY, json TEST NOT NULL ); diff --git a/t/tests/datastore.t b/t/tests/datastore.t new file mode 100644 index 0000000..101b125 --- /dev/null +++ b/t/tests/datastore.t @@ -0,0 +1,174 @@ +#!perl +use 5.024; +use strict; +use warnings; +use Test2::Bundle::Extended; +use experimental 'signatures'; +use Path::Tiny; +use AniDB::Datastore; + +my $tempdir = Path::Tiny->tempdir; +my $db_file = $tempdir->child('test.db'); + +my $ds; + +subtest 'construction' => sub { + try_ok { + $ds = AniDB::Datastore->new({database=>$db_file}) + } 'builds'; + + isa_ok( + $ds->dbh, + ['DBI::db'], + 'gets a dbh', + ); +}; + +my $test_file = $tempdir->child('some.video'); +$test_file->spew_raw('data'); +my $stat = $test_file->stat; + +subtest 'reading non-existent records' => sub { + is( + $ds->path_info_for($test_file), + undef, + 'path record', + ); + is( + $ds->episode_info_for({hash=>1,size=>1}), + undef, + 'episode record', + ); + is( + $ds->anime_info_for({aid=>1}), + undef, + 'anime record', + ); +}; + +subtest 'insert data' => sub { + try_ok { + ok $ds->update_path_info( + $test_file => { + size => 1, + mtime => 1, + hash => 'abc', + }, + ); + } 'path info'; + + try_ok { + ok $ds->update_episode_info( + { + size => 1, + hash => 'abc', + }, + { random => 'info', aid => 5 }, + ); + } 'episode info'; + + try_ok { + ok $ds->update_anime_info( + { aid => 5 }, + { more => 'random' }, + ); + } 'anime info'; +}; + +subtest 'update data' => sub { + try_ok { + ok $ds->update_path_info( + $test_file => { + size => 3, + mtime => 5, + hash => 'abc', + }, + ); + } 'path info'; + + try_ok { + ok $ds->update_episode_info( + { + size => 3, + hash => 'abc', + }, + { random => 'new-info', aid => 5 }, + ); + } 'episode info'; + + try_ok { + ok $ds->update_anime_info( + { aid => 5 }, + { more => 'new-random' }, + ); + } 'anime info'; +}; + +subtest 'retrieve data' => sub { + is( + $ds->path_info_for($test_file), + my $path_info = { + name => $test_file->stringify, + size => 3, + mtime => 5, + hash => 'abc', + + }, + 'path info', + ); + + is( + $ds->episode_info_for({size=>3,hash=>'abc'}), + my $episode_info ={ random => 'new-info', aid => 5 }, + 'episode info', + ); + + is( + $ds->anime_info_for({ aid => 5 }), + my $anime_info = { more => 'new-random' }, + 'anime info', + ); + + is( + $ds->full_info_for($test_file), + { + path => $path_info, + episode => $episode_info, + anime => $anime_info, + }, + 'full info', + ); +}; + +subtest 'changed' => sub { + ok $ds->has_changed($test_file,$stat); + + $ds->update_path_info( + $test_file => { + size => $stat->size, + mtime => $stat->mtime, + hash => 'abcd', + }, + ); + + ok not $ds->has_changed($test_file,$stat); +}; + +subtest 'rename' => sub { + my $new_name = path('/tmp/another'); + $ds->rename($test_file,$new_name); + is( + $ds->path_info_for($new_name), + { + name => $new_name->stringify, + size => $stat->size, + mtime => $stat->mtime, + hash => 'abcd', + }, + 'path info', + ); +}; + +done_testing; + + -- cgit v1.2.3