BusyBird
view release on metacpan or search on metacpan
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
package BusyBird::Test::StatusStorage;
use v5.8.0;
use strict;
use warnings;
use Exporter 5.57 qw(import);
use DateTime;
use DateTime::Duration;
use Test::More;
use Test::Builder;
use Test::Fatal 0.006 qw(dies_ok);
use BusyBird::DateTime::Format;
use BusyBird::StatusStorage;
use BusyBird::Util ();
use Carp;
use utf8;
use Encode qw(encode_utf8);
our %EXPORT_TAGS = (
storage => [
qw(test_storage_common test_storage_ordered test_storage_truncation test_storage_missing_arguments),
qw(test_storage_requires_status_ids test_storage_undef_in_array),
],
status => [qw(test_status_id_set test_status_id_list)],
);
our @EXPORT_OK = ();
BusyBird::Util::export_ok_all_tags();
push @EXPORT_OK, qw(test_cases_for_ack);
my $datetime_formatter = 'BusyBird::DateTime::Format';
sub status {
my ($id, $level, $acked_at) = @_;
croak "you must specify id" if not defined $id;
my $status = {
id => $id,
created_at => $datetime_formatter->format_datetime(
DateTime->from_epoch(epoch => $id)
),
};
$status->{busybird}{level} = $level if defined $level;
$status->{busybird}{acked_at} = $acked_at if defined $acked_at;
return $status;
}
sub nowstring {
return $datetime_formatter->format_datetime(
DateTime->now(time_zone => 'UTC')
);
}
sub add_datetime_days {
my ($datetime_str, $days) = @_;
my $dtd = DateTime::Duration->new(days => ($days > 0 ? $days : -$days));
my $orig_dt = $datetime_formatter->parse_datetime($datetime_str);
return $datetime_formatter->format_datetime(
($days > 0) ? ($orig_dt + $dtd) : ($orig_dt - $dtd)
);
}
sub id_counts {
my @statuses_or_ids = @_;
my %id_counts = ();
foreach my $s_id (@statuses_or_ids) {
my $id = ref($s_id) ? $s_id->{id} : $s_id;
$id_counts{$id} += 1;
}
return %id_counts;
}
sub id_list {
my @statuses_or_ids = @_;
return map { ref($_) ? $_->{id} : $_ } @statuses_or_ids;
}
sub acked {
my ($s) = @_;
no autovivification;
return $s->{busybird}{acked_at};
}
sub test_status_id_set {
## unordered status ID set test
my ($got_statuses, $exp_statuses_or_ids, $msg) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
return is_deeply(
{ id_counts @$got_statuses },
{ id_counts @$exp_statuses_or_ids },
$msg
);
}
sub test_status_id_list {
## ordered status ID list test
my ($got_statuses, $exp_statuses_or_ids, $msg) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
return is_deeply(
[id_list @$got_statuses],
[id_list @$exp_statuses_or_ids],
$msg
);
}
sub sync_get {
my ($storage, $loop, $unloop, %query) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $callbacked = 0;
my $statuses;
$storage->get_statuses(%query, callback => sub {
my $error = shift;
$statuses = shift;
is($error, undef, 'operation succeed');
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, 'callbacked');
return $statuses;
}
sub sync_get_unacked_counts {
my ($storage, $loop, $unloop, $timeline) = @_;
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $callbacked = 0;
my $result;
$storage->get_unacked_counts(
timeline => $timeline, callback => sub {
my ($error, $unacked_counts) = @_;
is($error, undef, 'operation succeed');
$result = $unacked_counts;
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, 'callbacked');
return %$result;
}
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
{ sync_get_unacked_counts($storage, $loop, $unloop, $tl) },
{ total => 0 },
"$tl is empty"
);
}
note("--- put_statuses (insert), single");
$callbacked = 0;
$storage->put_statuses(
timeline => '_test_tl1',
mode => 'insert',
statuses => status(1),
callback => sub {
my ($error, $num) = @_;
is($error, undef, 'put_statuses succeed.');
is($num, 1, 'put 1 status');
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
is_deeply(
{ sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
{ total => 1, 0 => 1 },
'1 unacked status'
);
note('--- put_statuses (insert), multiple');
$callbacked = 0;
$storage->put_statuses(
timeline => '_test_tl1',
mode => 'insert',
statuses => [map { status($_) } 2..5],
callback => sub {
my ($error, $num) = @_;
is($error, undef, 'put_statuses succeed');
is($num, 4, 'put 4 statuses');
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
is_deeply(
{ sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
{ total => 5, 0 => 5 },
'5 unacked status'
);
note('--- get_statuses: any, all');
$callbacked = 0;
$storage->get_statuses(
timeline => '_test_tl1',
count => 'all',
callback => sub {
my ($error, $statuses) = @_;
is($error, undef, "get_statuses succeed");
test_status_id_set($statuses, [1..5], "1..5 statuses");
foreach my $s (@$statuses) {
no autovivification;
ok(!$s->{busybird}{acked_at}, "status is not acked");
}
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
note('--- ack_statuses: all');
$callbacked = 0;
$storage->ack_statuses(
timeline => '_test_tl1',
callback => sub {
my ($error, $num) = @_;
is($error, undef, "ack_statuses succeed");
is($num, 5, "5 statuses acked.");
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
is_deeply(
{ sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
{ total => 0 },
"all acked"
);
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl1', count => 'all'
}, sub {
my $statuses = shift;
is(int(@$statuses), 5, "5 statueses");
foreach my $s (@$statuses) {
no autovivification;
ok($s->{busybird}{acked_at}, 'acked');
}
};
note('--- delete_statuses (single deletion)');
$callbacked = 0;
$storage->delete_statuses(
timeline => '_test_tl1',
ids => 3,
callback => sub {
my ($error, $num) = @_;
is($error, undef, "operation succeed.");
is($num, 1, "1 deletion");
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl1', count => 'all'
}, sub {
my $statuses = shift;
test_status_id_set($statuses, [1,2,4,5], "ID=3 is deleted");
};
note('--- delete_statuses (multiple deletion)');
$callbacked = 0;
$storage->delete_statuses(
timeline => '_test_tl1',
ids => [1, 4],
callback => sub {
my ($error, $num) = @_;
is($error, undef, 'operation succeed');
is($num, 2, "2 statuses deleted");
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl1', count => 'all'
}, sub {
my $statuses = shift;
test_status_id_set($statuses, [2,5], "ID=1,4 are deleted");
};
note('--- delete_statuses (all deletion)');
$callbacked = 0;
$storage->delete_statuses(
timeline => '_test_tl1',
ids => undef,
callback => sub {
my ($error, $num) = @_;
is($error, undef, 'operation succeed');
is($num, 2, "2 statuses deleted");
$callbacked = 1;
$unloop->();
}
);
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
is($error, undef, 'ack_statuses succeed');
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl1', count => 'all',
}, sub {
my $statuses = shift;
test_status_id_set($statuses, [1..5], "5 statuses");
foreach my $s (@$statuses) {
ok(acked($s), "Status ID = $s->{id} is acked");
}
};
note('--- put (insert): try to insert existent status');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'insert', target => status(3), exp_change => 0,
exp_unacked => [], exp_acked => [1..5]
);
note('--- put (update): change to unacked');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'update', target => [map {status($_)} (2,4)], exp_change => 2,
exp_unacked => [2,4], exp_acked => [1,3,5]
);
note('--- put (update): change to unacked');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'update', target => [map { status($_) } (3,5)],
exp_change => 2, exp_unacked => [2,3,4,5], exp_acked => [1]
);
is_deeply(
{sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
{total => 4, 0 => 4}, '4 unacked statuses'
);
note('--- put (update): change level');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'update',
target => [map { status($_, ($_ % 2 + 1), $_ == 1 ? nowstring() : undef) } (1..5)],
exp_change => 5, exp_unacked => [2,3,4,5], exp_acked => [1]
);
is_deeply(
{sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
{total => 4, 1 => 2, 2 => 2}, "4 unacked statuses in 2 levels"
);
note('--- put (upsert): acked statuses');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'upsert', target => [map { status($_, 7, nowstring()) } (4..7)],
exp_change => 4, exp_unacked => [2,3], exp_acked => [1,4..7]
);
note('--- get and put(update): back to unacked');
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl1', count => 'all', ack_state => 'acked'
}, sub {
my $statuses = shift;
delete $_->{busybird}{acked_at} foreach @$statuses;
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl1',
mode => 'update', target => $statuses,
exp_change => 5, exp_unacked => [1..7], exp_acked => []
);
};
is_deeply(
{sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
{total => 7, 1 => 1, 2 => 2, 7 => 4}, "3 levels"
);
note('--- put(insert): to another timeline');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl2',
mode => 'insert', target => [map { status($_) } (1..10)],
exp_change => 10, exp_unacked => [1..10], exp_acked => []
);
is_deeply(
{sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl2')},
{total => 10, 0 => 10}, '10 unacked statuses'
);
## change_and_check(
## $storage, $loop, $unloop, timeline => '_test_tl2',
## mode => 'ack', target => [1..5],
## exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
## );
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl2',
mode => 'update', target => [map {status($_, undef, nowstring())} (1..5)],
exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
);
note('--- get: single, any state');
foreach my $id (1..10) {
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl2', count => 1, max_id => $id
}, sub {
my $statuses = shift;
is(int(@$statuses), 1, "get 1 status");
is($statuses->[0]{id}, $id, "... and its ID is $id");
};
}
note('--- get: single, specific state');
foreach my $id (1..10) {
my $correct_state = ($id <= 5) ? 'acked' : 'unacked';
my $wrong_state = $correct_state eq 'acked' ? 'unacked' : 'acked';
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl2', count => 1, max_id => $id,
ack_state => $correct_state,
}, sub {
my $statuses = shift;
is(int(@$statuses), 1, "get 1 status");
is($statuses->[0]{id}, $id, "... and its ID is $id");
};
foreach my $count ('all', 1, 10) {
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl2', count => $count, max_id => $id,
ack_state => $wrong_state
}, sub {
my $statuses = shift;
is(int(@$statuses), 0,
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
});
$loop->();
ok($callbacked, 'callbacked');
my $already_acked_at = nowstring();
change_and_check(
$storage, $loop, $unloop, timeline => '_test_acks', mode => 'insert',
target => [(map {status($_, 0, $already_acked_at)} 1..10), (map {status($_)} 11..20)],
exp_change => 20, exp_acked => [1..10], exp_unacked => [11..20]
);
my %target_args = ();
$target_args{target_ids} = $case->{req}{ids} if exists $case->{req}{ids};
$target_args{target_max_id} = $case->{req}{max_id} if exists $case->{req}{max_id};
change_and_check(
$storage, $loop, $unloop, timeline => '_test_acks', mode => 'ack',
%target_args, exp_change => $case->{exp_count}, exp_acked => $case->{exp_acked}, exp_unacked => $case->{exp_unacked}
);
}
}
{
note('--- -- -- Unicode timeline name and Unicode status ID');
foreach my $timeline_name ("_test_ascii", '_test_ãã«ãã¼ã©') {
note(encode_utf8("--- -- timeline: $timeline_name"));
$storage->delete_statuses(timeline => $timeline_name, ids => undef, callback => sub {
my $e = shift;
is($e, undef, "initial delete");
$unloop->();
});
$loop->();
my @statuses = map { status($_) } 0..1;
$statuses[1]{id} = 'ãã¡';
$statuses[0]{text} = 'ããã¹ã ã¼ã';
$statuses[1]{text} = 'ããã¹ã ãã¡';
change_and_check(
$storage, $loop, $unloop, timeline => $timeline_name, mode => 'insert', target => \@statuses,
exp_change => 2, exp_acked => [], exp_unacked => [qw(0 ãã¡)]
);
check_contains($storage, $loop, $unloop,
{timeline => $timeline_name, query => [$statuses[1], 'ã«', 'ãã¡', 0]},
[undef, [$statuses[1], 'ãã¡', 0], ['ã«']],
encode_utf8("contains() works fine with Unicode timeline $timeline_name and Unicode status IDs"));
$storage->get_unacked_counts(timeline => $timeline_name, callback => sub {
my ($e, $unacked_counts) = @_;
is($e, undef, "get unacked counts succeed");
is_deeply($unacked_counts, {total => 2, 0 => 2}, "unacked counts OK");
$unloop->();
});
$loop->();
change_and_check(
$storage, $loop, $unloop, timeline => $timeline_name, mode => 'ack', target_ids => $statuses[1]{id},
exp_change => 1, exp_acked => ["ãã¡"], exp_unacked => [0]
);
foreach my $status (@statuses) {
my $got_statuses = sync_get(
$storage, $loop, $unloop,
timeline => $timeline_name, count => 1, max_id => $status->{id}
);
test_status_id_list($got_statuses, [$status->{id}], encode_utf8("status ID $status->{id} OK"));
is($got_statuses->[0]{text}, $status->{text}, encode_utf8("status text '$status->{text}' OK"));
if($got_statuses->[0]{id} eq "0") {
ok(!$got_statuses->[0]{busybird}{acked_at}, "status 0 is not acked");
}else {
ok($got_statuses->[0]{busybird}{acked_at}, "status 1 is acked");
}
}
$statuses[1]{busybird}{level} = 5;
change_and_check(
$storage, $loop, $unloop, timeline => $timeline_name, mode => "update", target => $statuses[1],
exp_change => 1, exp_acked => [], exp_unacked => [0, "ãã¡"]
);
$storage->get_unacked_counts(timeline => $timeline_name, callback => sub {
my ($e, $unacked_counts) = @_;
is($e, undef, "get unacked counts succeed");
is_deeply($unacked_counts, {total => 2, 0 => 1, 5 => 1}, "unacked counts OK");
$unloop->();
});
$loop->();
change_and_check(
$storage, $loop, $unloop, timeline => $timeline_name, mode => "delete", target => [map {$_->{id}} @statuses],
exp_change => 2, exp_acked => [], exp_unacked => []
);
}
}
note('--- clean up');
foreach my $tl ('_test_tl1', '_test_tl2') {
$callbacked = 0;
$storage->delete_statuses(timeline => $tl, ids => undef, callback => sub {
my $error= shift;
is($error, undef, "operation succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, "callbacked");
}
}
sub test_storage_ordered {
my ($storage, $loop, $unloop) = @_;
$loop ||= sub {};
$unloop ||= sub {};
note('-------- test_storage_ordered');
note('--- clear timeline');
my $callbacked = 0;
foreach my $tl (qw(_test_tl3 _test_tl4 _test_tl5)) {
$callbacked = 0;
$storage->delete_statuses(timeline => $tl, ids => undef, callback => sub {
my $error = shift;
is($error, undef, "operation succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, "callbacked");
}
note('--- acked_at and created_at are preserved');
foreach my $case (
{label => "both unset", created_at => undef, acked_at => undef},
{label => "only created_at set", created_at => 'Mon Jul 01 22:11:41 +0900 2013',
acked_at => undef},
{label => "only acked_at set", created_at => undef,
acked_at => "Wed Apr 17 04:23:29 -0500 2013"},
{label => 'both set', created_at => 'Fri Oct 12 00:36:44 +0000 2012',
acked_at => 'Thu Oct 25 13:10:00 +0200 2012'},
) {
note("--- -- case: $case->{label}");
$callbacked = 0;
my $status = status(1);
$status->{created_at} = $case->{created_at};
$status->{busybird}{acked_at} = $case->{acked_at};
$storage->put_statuses(
timeline => "_test_tl3", mode => 'insert', statuses => $status,
callback => sub {
my ($error, $count) = @_;
is($error, undef, "put succeed");
is($count, 1, "1 inserted");
$callbacked = 1;
$unloop->();
}
);
$loop->();
ok($callbacked, "callbacked");
on_statuses $storage, $loop, $unloop, {timeline => '_test_tl3', count => 'all'}, sub {
my $statuses = shift;
is(scalar(@$statuses), 1, "1 status obtained");
is($statuses->[0]{created_at}, $case->{created_at}, "created_at is preserved");
is($statuses->[0]{busybird}{acked_at}, $case->{acked_at}, "acked_at is preserved");
};
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'delete', target => undef, exp_change => 1,
exp_unacked => [], exp_acked => []
);
}
note('--- populate timeline');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'insert', target => [map {status $_} (1..30)],
label => 'first insert',
exp_change => 30, exp_unacked => [1..30], exp_acked => []
);
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'ack', target => undef, label => 'ack all',
exp_change => 30, exp_unacked => [], exp_acked => [1..30]
);
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'insert', target => [map {status $_} (31..60)],
label => "another insert", exp_change => 30,
exp_unacked => [31..60], exp_acked => [1..30]
);
my %base = (timeline => '_test_tl3');
get_and_check_list(
$storage, $loop, $unloop, {%base, count => 'all'}, [reverse 1..60],
'get: no max_id, any state, all'
);
get_and_check_list(
$storage, $loop, $unloop, {%base, count => 20}, [reverse 41..60],
'get: no max_id, any state, partial'
);
get_and_check_list(
$storage, $loop, $unloop, {%base, count => 40}, [reverse 21..60],
'get: no max_id, any state, both states'
);
get_and_check_list(
$storage, $loop, $unloop, {%base, count => 120}, [reverse 1..60],
'get: no max_id, any state, count larger than the size'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', count => 'all'},
[reverse 31..60],
'get: no max_id unacked, all'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', count => 15},
[reverse 46..60 ],
'get: no max_id, unacked, partial'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', count => 50},
[reverse 31..60],
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', max_id => 20, count => 5},
[],
'get: max_id in acked, unacked state'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 50, count => 10},
[],
'get: max_id in unacked, acked state, count in unacked'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 45, count => 30},
[],
'get: max_id in unacked, acked state, count larger than the unacked size'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 20, count => 10},
[reverse 11..20],
'get: max_id in acked, acked state, count in acked'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 10, count => 30},
[reverse 1..10],
'get: max_id in acked, acked state, count larger than acked size'
);
{
note('--- more acked statuses');
my $now = DateTime->now(time_zone => 'UTC');
my $yesterday = $now - DateTime::Duration->new(days => 1);
my $tomorrow = $now + DateTime::Duration->new(days => 1);
my @more_statuses = (
(map { status $_, 0, $datetime_formatter->format_datetime($tomorrow) } 61..70),
(map { status $_, 0, $datetime_formatter->format_datetime($yesterday) } 71..80)
);
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'insert', target => \@more_statuses,
exp_change => 20, exp_unacked => [31..60], exp_acked => [1..30, 61..80]
);
}
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'any', count => 'all'},
[reverse(71..80, 1..30, 61..70, 31..60)],
'get: mixed acked_at, no max_id, any state, all'
);
note('--- move from acked to unacked');
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl3', acked_state => 'acked',
max_id => 30, count => 10
}, sub {
my $statuses = shift;
delete $_->{busybird}{acked_at} foreach @$statuses;
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'update', target => $statuses,
exp_change => 10,
exp_unacked => [21..60], exp_acked => [1..20, 61..80]
);
};
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'any', count => 'all'},
[reverse(71..80, 1..20, 61..70, 21..60)],
'get:mixed acked_at, no max_id, any state, all'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'any', max_id => 30, count => 30},
[reverse(11..20, 61..70, 21..30)],
'get:mixed acked_at, max_id in unacked, any state, count larger than unacked size'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'any', max_id => 15, count => 20},
[reverse(76..80, 1..15)],
'get:mixed acked_at, max_id in acked, any state, count in acked'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', max_id => 50, count => 50},
[reverse(21..50)],
'get:mixed acked_at, max_id in unacked, unacked state, count larger than unacked size'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 65, count => 30},
[reverse(76..80, 1..20, 61..65)],
'get:mixed acked_at, max_id in acked, acked state, count in acked area'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'unacked', max_id => 20, count => 30},
[],
'get:mixed acked_at, max_id in acked, unacked state'
);
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'acked', max_id => 40, count => 30},
[],
'get:mixed acked_at, max_id in unacked, acked state'
);
note('--- messing with created_at');
on_statuses $storage, $loop, $unloop, {
timeline => '_test_tl3', count => 'all'
}, sub {
my $statuses = shift;
is(int(@$statuses), 80, "80 statuses");
foreach my $s (@$statuses) {
$s->{created_at} = $datetime_formatter->format_datetime(
$datetime_formatter->parse_datetime($s->{created_at})
+ DateTime::Duration->new(days => 100 - $s->{id})
);
}
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl3',
mode => 'update', target => $statuses, exp_change => 80,
exp_unacked => [21..60], exp_acked => [1..20, 61..80]
);
};
get_and_check_list(
$storage, $loop, $unloop,
{%base, ack_state => 'any', count => 'all'},
[21..60, 61..70, 1..20, 71..80],
'sorted by descending order of created_at within acked_at group'
);
note('--- -- ack test');
note('--- change acked_at for testing');
on_statuses $storage, $loop, $unloop, {
%base, count => 'all', ack_state => 'acked'
}, sub {
my $statuses = shift;
foreach my $s (@$statuses) {
$s->{busybird}{acked_at} =
add_datetime_days($s->{busybird}{acked_at}, +2);
}
change_and_check(
$storage, $loop, $unloop, %base, mode => 'update',
target => $statuses, exp_change => 40,
exp_unacked => [21..60], exp_acked => [61..70, 1..20, 71..80]
);
};
change_and_check(
$storage, $loop, $unloop, %base, mode => 'ack', target => 51,
exp_change => 10, exp_unacked => [21..50], exp_acked => [61..70, 1..20, 71..80, 51..60]
);
get_and_check_list(
$storage, $loop, $unloop, {%base, ack_state => 'any', count => 'all'},
[21..50, 61..70, 1..20, 71..80, 51..60],
'10 acked statuses are at the bottom, because other acked statuses have acked_at of future.'
);
note('--- populate another timeline');
my %base4 = (timeline => '_test_tl4');
$callbacked = 0;
$storage->delete_statuses(%base4, ids => undef, callback => sub {
my $error = shift;
is($error, undef, "delete succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, "callbacked");
change_and_check(
$storage, $loop, $unloop, %base4,
mode => 'insert', target => [map {status($_)} (31..40)],
exp_change => 10, exp_unacked => [31..40], exp_acked => []
);
get_and_check_list(
$storage, $loop, $unloop, {%base4, count => 'all'}, [reverse 31..40],
'10 unacked'
);
change_and_check(
$storage, $loop, $unloop, %base4,
mode => 'ack', target => 35, exp_change => 5,
exp_unacked => [36..40], exp_acked => [31..35]
);
get_and_check_list(
$storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'acked'},
[reverse 31..35], '5 acked'
);
change_and_check(
$storage, $loop, $unloop, %base4,
mode => 'insert', target => [map {status($_)} (26..30, 41..45)],
exp_change => 10, exp_unacked => [26..30, 36..45], exp_acked => [31..35]
);
get_and_check_list(
$storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'unacked'},
[reverse 26..30, 36..45], '15 unacked statuses'
);
note('--- For testing, set acked_at sufficiently old.');
on_statuses $storage, $loop, $unloop, {
%base4, count => 'all', ack_state => 'acked'
}, sub {
my $statuses = shift;
foreach my $s (@$statuses) {
$s->{busybird}{acked_at} = add_datetime_days($s->{busybird}{acked_at}, -1);
}
change_and_check(
$storage, $loop, $unloop, %base4, mode => 'update', target => $statuses,
exp_change => 5, exp_unacked => [26..30, 36..45], exp_acked => [31..35]
);
};
change_and_check(
$storage, $loop, $unloop, %base4, mode => 'ack', target => 40, exp_change => 10,
exp_unacked => [41..45], exp_acked => [36..40, 26..30, 31..35]
);
get_and_check_list(
$storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'acked'},
[reverse(36..40), reverse(26..30), reverse(31..35)]
);
change_and_check(
$storage, $loop, $unloop, %base4, mode => 'ack', exp_change => 5,
exp_unacked => [], exp_acked => [26..45]
);
{
note('--- same timestamp: order is free, but must be consistent.');
my %base5 = (timeline => '_test_tl5');
my @in_statuses = map {status($_)} (1..10);
my $created_at = nowstring;
$_->{created_at} = $created_at foreach @in_statuses;
change_and_check(
$storage, $loop, $unloop, %base5, mode => 'insert', target => [@in_statuses[0..4]],
label => 'insert first five', exp_change => 5, exp_unacked => [1..5], exp_acked => []
);
change_and_check(
$storage, $loop, $unloop, %base5, mode => 'ack', target => undef,
label => 'ack first five', exp_change => 5, exp_unacked => [], exp_acked => [1..5]
);
change_and_check(
$storage, $loop, $unloop, %base5, mode => 'insert', target => [@in_statuses[5..9]],
label => 'insert next five', exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
);
my $whole_timeline = sync_get($storage, $loop, $unloop, %base5, count => 'all');
foreach my $start_index (0..9) {
my $max_id = $whole_timeline->[$start_index]{id};
get_and_check_list(
$storage, $loop, $unloop, {%base5, count => 'all', max_id => $max_id},
[ map {$_->{id}} @{$whole_timeline}[$start_index .. 9] ],
"start_index = $start_index, max_id = $max_id: order is the same as the whole_timeline"
);
}
}
{
note('--- -- acks with various argument cases (ordered)');
foreach my $case (test_cases_for_ack(is_ordered => 1)) {
my $callbacked = 0;
next if not defined $case->{req};
note("--- case: $case->{label}");
$storage->delete_statuses(timeline => '_test_acks', ids => undef, callback => sub {
my ($error, $count) = @_;
is($error, undef, "delete succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
lib/BusyBird/Test/StatusStorage.pm view on Meta::CPAN
note("--- soft_max = $soft_max, hard_max = $hard_max");
$loop ||= sub {};
$unloop ||= sub {};
note('--- clear the timeline');
my $callbacked = 0;
my %base = (timeline => '_test_tl4');
$storage->delete_statuses(%base, ids => undef, callback => sub {
my $error = shift;
is($error, undef, "delete succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, 'callbacked');
on_statuses $storage, $loop, $unloop, {
%base, count => 'all'
}, sub {
my ($statuses) = @_;
is(int(@$statuses), 0, 'no statuses');
};
note('--- populate to the max');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => [map {status($_)} (1..$hard_max)],
exp_change => $hard_max, exp_unacked => [1..$hard_max],
exp_acked => []
);
note('--- insert another one: truncation occurs');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => status($hard_max+1),
exp_change => 1, exp_unacked => [($hard_max+1 - ($soft_max-1))..($hard_max+1)],
exp_acked => []
);
note('--- insert multiple statuses: truncation occurs');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => [map { status($_) } ($hard_max+2) .. ($hard_max*2 - $soft_max + 11)],
exp_change => ($hard_max - $soft_max + 10),
exp_unacked => [($hard_max*2 - $soft_max*2 + 12) .. ($hard_max*2 - $soft_max + 11)],
exp_acked => []
);
note('--- clear and populate to the max');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'delete', target => undef,
exp_change => $soft_max, exp_unacked => [], exp_acked => []
);
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => [map {status($_)} 1..$hard_max],
exp_change => $hard_max, exp_unacked => [1..$hard_max], exp_acked => []
);
note('--- ack the top status');
on_statuses $storage, $loop, $unloop, {
%base, count => 1, max_id => $hard_max
}, sub {
my ($statuses) = @_;
$statuses->[0]{busybird}{acked_at} = nowstring();
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'update', target => $statuses,
exp_change => 1, exp_unacked => [1..($hard_max-1)],
exp_acked => [$hard_max]
);
};
note('--- inserting another one removes the acked status');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => status($hard_max+1),
exp_change => 1, exp_unacked => [($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)],
exp_acked => []
);
note('--- populate timeline to the max');
change_and_check(
$storage, $loop, $unloop, %base,
mode => 'insert', target => [map {status($_)} ($hard_max+2) .. ($hard_max+2 + $hard_max - $soft_max - 1)],
exp_change => $hard_max - $soft_max,
exp_unacked => [($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)..($hard_max*2+2 - $soft_max-1)],
exp_acked => []
);
note('--- clear another timeline');
$storage->delete_statuses(timeline => '_test_tl4_2', ids => undef, callback => sub {
my $error = shift;
is($error, undef, "delete succeed");
$callbacked = 1;
$unloop->();
});
$loop->();
ok($callbacked, "callbacked");
note('--- populate another timeline to the max');
change_and_check(
$storage, $loop, $unloop, timeline => '_test_tl4_2',
mode => 'insert', target => [map {status($_)} 1..$hard_max],
exp_change => $hard_max, exp_unacked => [1..$hard_max], exp_acked => []
);
note('--- statuses in the first timeline is maintained.');
on_statuses $storage, $loop, $unloop, {
%base, count => 'all'
}, sub {
my $statuses = shift;
test_status_id_list(
$statuses, [reverse( ($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)..($hard_max*2+2 - $soft_max-1) )],
"statuses in the first timeline are intact"
);
};
}
sub test_storage_missing_arguments {
my ($storage, $loop, $unloop) = @_;
note("-------- test_storage_missing_arguments");
dies_ok { $storage->ack_statuses() } 'ack: timeline is missing';
dies_ok { $storage->get_statuses(callback => sub {}) } 'get: timeline is missing';
dies_ok { $storage->get_statuses(timeline => 'tl') } 'get: callback is missing';
dies_ok {
$storage->put_statuses(mode => 'insert', statuses => []);
} 'put: timeline is missing';
dies_ok {
$storage->put_statuses(timeline => 'tl', mode => 'insert');
( run in 1.573 second using v1.01-cache-2.11-cpan-39bf76dae61 )