#!/usr/bin/perl
use strict;
use warnings;
use inc::testplan(1,
+ 4 # use
+ 199
+ 610
+ 7 # callbacks
+ 194 # fin
+ 206 # one sync+verify
);
########################################################################
BEGIN {
use_ok('dtRdr::Annotation::IO');
use_ok('dtRdr::Annotation::Sync::Standard');
use_ok('dtRdr::Config');
use_ok('dtRdr::BookBag');
}
########################################################################
# sync only these book id's
my @book_list = qw(
xEF22EA3CDB3611DBA417B36A7806B258
xF0D8058ADB3911DB8F2B386E7806B258
);
########################################################################
use test_lib::server;
use test_inc::tempdir;
my $s_storage_dir = wants 't/_sync/s_temp';
my $l_storage_dir = wants 't/_sync/l_temp';
use test_inc::anno_copy;
{
my %books_ok = map({$_ => 1} @book_list);
anno_copy(
'test_data/annotations.server.01/',
$s_storage_dir,
sub {$_->{public} and $books_ok{$_->{book}}},
);
}
########################################################################
########################################################################
# setup the server *** caution, forks! ***
srand;
my $user = 'bob';
my $pass = join('', map({chr(97 + int(rand(25)))} 0..12));
my $url = test_lib::server->new(
storage => $s_storage_dir,
users => {$user => $pass},
auth_type => $ENV{DOT_SERVER_AUTH} || 'Basic',
verbose => $ENV{DOT_SERVER_VERBOSE} || 0,
#auth_required => 1,
#no_auth => 1, # XXX not really working?
)->started_ok or die 'no server';
$url .= '/';
########################################################################
# just a basic pull sync, thus: an unpopulated local io
my $io = dtRdr::Annotation::IO->new(uri => $l_storage_dir);
my $server = dtRdr::ConfigData::Server->new(
id => 'the server',
username => $user,
password => $pass,
uri => $url,
);
########################################################################
my $run_sync = sub {
my (%opts) = @_;
my $sync = dtRdr::Annotation::Sync::Standard->new(
anno_io => $io,
server => $server,
books => [@book_list],
%opts
);
# run it
$sync->start;
my $counter = 0;
until($sync->done) {
$sync->work;
($counter++ > 1000) and last; # just to have some limit
}
ok($sync->done, 'done') or die "latency trouble?";
}; # end sub $run_sync
########################################################################
my $get_yml = sub {
my ($id) = @_;
return YAML::Syck::LoadFile(
'test_data/annotations.input.01/' . $id .'.yml');
};
use test_inc::anno_io_verify;
my $verify = sub {
anno_io_verify($s_storage_dir, $io, $server, \@book_list);
};
$run_sync->();
$verify->();
use dtRdr::Book;
sub zombie_anno {
my ($h, $io) = @_;
my $book = dtRdr::Book::Zombie->new(id => $h->{book});
my $type = $h->{type};
# load if needed
my $anno_type = dtRdr::Annotation::IO->_anno_type($type);
my $anno = $type->deserialize($h, book => $book);
if($io) {
my $method = 'add_' . $anno_type;
$book->$method($anno);
$book->set_anno_io($io);
}
return($anno);
} # end subroutine zombie_anno
########################################################################
# now modify some of these locally
{
my $id = 'AAAB';
my $rev;
{
my $zanno = zombie_anno(grep({$_->{id} eq $id} $io->items), $io);
$rev = $zanno->revision;
ok(defined($rev), 'has a revision');
# make sure it is indeed mine
defined($zanno->public->owner) and die "bad test data?";
$zanno->set_content($zanno->content . 'thbbt');
$zanno->book->change_note($zanno);
is($zanno->revision, $rev+1, 'rev bump');
}
{ # check the io
my ($obj) = grep({$_->{id} eq $id} $io->items);
is($obj->{revision}, $rev+1, 'rev bump persists');
}
}
$run_sync->();
$verify->();
# and add
{
my $anno = $get_yml->('Q');
$io->x_insert($anno->{id}, $anno);
my $rev;
{
my $zanno = zombie_anno($anno, $io);
$rev = $zanno->revision;
ok(defined($rev), 'has a revision');
is($rev, 0, 'correct');
# make sure it is indeed mine
defined($zanno->public->owner) and die "bad test data?";
}
{ # check the io
my ($obj) = grep({$_->{id} eq $anno->{id}} $io->items);
is($obj->{revision}, $rev, 'rev persists');
}
}
$run_sync->();
$verify->();
# run it as many times as you like :-D
$run_sync->(); $verify->();
#$run_sync->(); $verify->(); # though you do have to change the counter
#$run_sync->(); $verify->();
# and delete
{
my $id = 'Q';
# TODO maybe shouldn't need to go through serialize to delete, but
# let's just play the game for now. (this deals with .yml.deleted)
my $zanno = zombie_anno(grep({$_->{id} eq $id} $io->items), $io);
$zanno->book->delete_note($zanno);
{ # check the io
my @found = grep({$_->{id} eq $id} $io->items);
ok(!@found, 'is gone');
my @del = grep({$_->{id} eq $id} $io->deleted);
is(scalar(@del), 1, 'all set');
}
}
$run_sync->();
$verify->();
# to deref
my $OBlob = sub {dtRdr::Annotation::IOBlob->outgoing(%{$_[0]});};
# do some deletes and mods from a different UA
# TODO maybe also from a different user
my $to_del = 'AAAB';
my $bm_del = 'x10426916C14E11DBAB43B801C9B462D6';
my $bm_mod = 'x0DA4CBC4C15111DB884BD901C9B462D6';
my $to_mod = 'A';
my $to_add = 'R';
{ # could create a completely new io but probably just do some puts
{
package MyUA;
use base 'LWP::UserAgent';
sub get_basic_credentials {return($user, $pass);}
# GRR, why can't I have delete and put methods?
sub req {
my $self = shift;
my $ans = $self->request(HTTP::Request->new(@_));
my %want = (
GET => 200,
POST => 201,
DELETE => 200,
PUT => 200,
);
($ans->code == $want{$_[0]}) or
die "bad answer ", $ans->code, " ", $ans->content;
my $cont = $ans->content;
return($cont);
}
}
my $ua = MyUA->new();
# OOPS, the other client needs to login when we're doing cookies
{
if(my $ans = $ua->req('GET', $url.'config.yml')) {
my $data = YAML::Syck::Load($ans);
if(my $lconf = $data->{login}) {
require HTTP::Cookies;
my $cookies = HTTP::Cookies->new;
$ua->cookie_jar($cookies);
my $template = $lconf->{template} or die "need template for login";
$template =~ s/#USERNAME#/$user/ or die "no #USERNAME# in template?";
$template =~ s/#PASSWORD#/$pass/ or die "no #PASSWORD# in template?";
my $ans = $ua->request(HTTP::Request->new(
'POST', $lconf->{url}, [], $template
));
}
}
}
$ua->req('DELETE', $url . "annotation/$to_del.yml?rev=1");
$ua->req('DELETE', $url . "annotation/$bm_del.yml?rev=0");
my $bm = $OBlob->(grep({$_->{id} eq $bm_mod} $io->items));
$bm or die "drat";
$bm->set_title('Test Coverage Rocks');
$bm->set_revision(1);
my $to = $OBlob->(grep({$_->{id} eq $to_mod} $io->items));
$to or die "drat";
$to->set_content($to->content . ' and and and and and');
eval { # didn't increment rev yet
$ua->req('PUT', $url . "annotation/$to_mod.yml?rev=0",
[content_type => 'text/x-yaml'],
YAML::Syck::Dump($to->deref)
);
};
my $err = $@;
like($err, qr/bad answer 409 Revision exists/);
$to->set_revision(1);
$ua->req('PUT', $url . "annotation/$to_mod.yml?rev=0",
[content_type => 'text/x-yaml'],
YAML::Syck::Dump($to->deref)
);
$ua->req('PUT', $url . "annotation/$bm_mod.yml?rev=0",
[content_type => 'text/x-yaml'],
YAML::Syck::Dump($bm->deref)
);
$ua->req('POST', $url . 'annotation/',
[content_type => 'text/x-yaml'],
do {open(my $fh, '<', 'test_data/annotations.input.01/R.yml');
local $/; <$fh>},
);
} # ugh, deleted, putted, posted
{ # setup book callbacks pointed at counters
my $s_book = dtRdr::Book::Zombie->new(id => $book_list[1]);
my $a_book = dtRdr::Book::Zombie->new(id => $book_list[0]);
$io->apply_to($s_book);
$io->apply_to($a_book);
my %hits;
my %changed;
foreach my $event (qw(created changed deleted)) {
$hits{$event} = 0;
my $setter = 'set_annotation_' . $event . '_sub';
dtRdr::Book->callback->$setter(sub {
my $anno = shift;
$hits{$event}++;
$changed{$anno->id} = $event;
});
}
my $bag = dtRdr::BookBag->new(books => [$s_book, $a_book]);
$run_sync->(bookbag => $bag);
$verify->();
is($hits{deleted}, 2);
is($hits{changed}, 2);
is($hits{created}, 1);
is_deeply(\%changed,
{
$to_del => 'deleted',
$bm_del => 'deleted',
$bm_mod => 'changed',
$to_mod => 'changed',
$to_add => 'created',
}, 'callbacks are golden');
{
my $anno = $a_book->find_bookmark($bm_mod);
is($anno->title, 'Test Coverage Rocks');
}
{
my $anno = $s_book->find_note($to_mod);
like($anno->content, qr/ and and and and and$/);
}
}
done;
# vim:ts=2:sw=2:et:sta:syntax=perl