File Coverage

blib/lib/BusyBird/Test/StatusStorage.pm
Criterion Covered Total %
statement 826 834 99.0
branch 49 58 84.4
condition 18 29 62.0
subroutine 128 130 98.4
pod 8 21 38.1
total 1029 1072 95.9


line stmt bran cond sub pod time code
1             package BusyBird::Test::StatusStorage;
2 7     7   389699 use v5.8.0;
  7         21  
  7         392  
3 7     7   37 use strict;
  7         11  
  7         259  
4 7     7   33 use warnings;
  7         10  
  7         297  
5 7     7   37 use Exporter 5.57 qw(import);
  7         114  
  7         251  
6 7     7   2754 use DateTime;
  7         337414  
  7         222  
7 7     7   53 use DateTime::Duration;
  7         11  
  7         154  
8 7     7   28 use Test::More;
  7         7  
  7         66  
9 7     7   1911 use Test::Builder;
  7         14  
  7         165  
10 7     7   2048 use Test::Fatal 0.006 qw(dies_ok);
  7         3293  
  7         450  
11 7     7   1809 use BusyBird::DateTime::Format;
  7         24281  
  7         187  
12 7     7   2134 use BusyBird::StatusStorage;
  7         12  
  7         137  
13 7     7   1593 use BusyBird::Util ();
  7         14  
  7         120  
14 7     7   35 use Carp;
  7         11  
  7         537  
15 7     7   38 use utf8;
  7         10  
  7         51  
16 7     7   3021 use Encode qw(encode_utf8);
  7         43530  
  7         3356  
17              
18             our %EXPORT_TAGS = (
19             storage => [
20             qw(test_storage_common test_storage_ordered test_storage_truncation test_storage_missing_arguments),
21             qw(test_storage_requires_status_ids test_storage_undef_in_array),
22             ],
23             status => [qw(test_status_id_set test_status_id_list)],
24             );
25             our @EXPORT_OK = ();
26              
27             BusyBird::Util::export_ok_all_tags();
28             push @EXPORT_OK, qw(test_cases_for_ack);
29              
30             my $datetime_formatter = 'BusyBird::DateTime::Format';
31              
32             sub status {
33 1829     1829 0 28934 my ($id, $level, $acked_at) = @_;
34 1829 50       3894 croak "you must specify id" if not defined $id;
35 1829         6300 my $status = {
36             id => $id,
37             created_at => $datetime_formatter->format_datetime(
38             DateTime->from_epoch(epoch => $id)
39             ),
40             };
41 1829 100       796168 $status->{busybird}{level} = $level if defined $level;
42 1829 100       4447 $status->{busybird}{acked_at} = $acked_at if defined $acked_at;
43 1829         6758 return $status;
44             }
45              
46             sub nowstring {
47 84     84 0 576 return $datetime_formatter->format_datetime(
48             DateTime->now(time_zone => 'UTC')
49             );
50             }
51              
52             sub add_datetime_days {
53 150     150 0 10597 my ($datetime_str, $days) = @_;
54 150 100       847 my $dtd = DateTime::Duration->new(days => ($days > 0 ? $days : -$days));
55 150         9666 my $orig_dt = $datetime_formatter->parse_datetime($datetime_str);
56 150 100       151998 return $datetime_formatter->format_datetime(
57             ($days > 0) ? ($orig_dt + $dtd) : ($orig_dt - $dtd)
58             );
59             }
60              
61             sub id_counts {
62 1580     1580 0 4865 my @statuses_or_ids = @_;
63 1580         2809 my %id_counts = ();
64 1580         3089 foreach my $s_id (@statuses_or_ids) {
65 17040 100       24229 my $id = ref($s_id) ? $s_id->{id} : $s_id;
66 17040         27562 $id_counts{$id} += 1;
67             }
68 1580         17790 return %id_counts;
69             }
70              
71             sub id_list {
72 844     844 0 2419 my @statuses_or_ids = @_;
73 844 100       1603 return map { ref($_) ? $_->{id} : $_ } @statuses_or_ids;
  10552         21191  
74             }
75              
76             sub acked {
77 4181     4181 0 6622 my ($s) = @_;
78 7     7   2169 no autovivification;
  7         5464  
  7         41  
79 4181         29457 return $s->{busybird}{acked_at};
80             }
81              
82             sub test_status_id_set {
83             ## unordered status ID set test
84 790     790 1 4025 my ($got_statuses, $exp_statuses_or_ids, $msg) = @_;
85 790         2170 local $Test::Builder::Level = $Test::Builder::Level + 1;
86 790         3670 return is_deeply(
87             { id_counts @$got_statuses },
88             { id_counts @$exp_statuses_or_ids },
89             $msg
90             );
91             }
92              
93             sub test_status_id_list {
94             ## ordered status ID list test
95 422     422 1 263616 my ($got_statuses, $exp_statuses_or_ids, $msg) = @_;
96 422         1112 local $Test::Builder::Level = $Test::Builder::Level + 1;
97 422         1863 return is_deeply(
98             [id_list @$got_statuses],
99             [id_list @$exp_statuses_or_ids],
100             $msg
101             );
102             }
103              
104             sub sync_get {
105 1173     1173 0 4147 my ($storage, $loop, $unloop, %query) = @_;
106 1173         2592 local $Test::Builder::Level = $Test::Builder::Level + 1;
107 1173         2088 my $callbacked = 0;
108 1173         1621 my $statuses;
109             $storage->get_statuses(%query, callback => sub {
110 1173     1173   2800 my $error = shift;
111 1173         2287 $statuses = shift;
112 1173         6854 is($error, undef, 'operation succeed');
113 1173         647508 $callbacked = 1;
114 1173         4345 $unloop->();
115 1173         10260 });
116 1173         8104 $loop->();
117 1173         4950 ok($callbacked, 'callbacked');
118 1173         434118 return $statuses;
119             }
120              
121             sub sync_get_unacked_counts {
122 27     27 0 85 my ($storage, $loop, $unloop, $timeline) = @_;
123 27         79 local $Test::Builder::Level = $Test::Builder::Level + 1;
124 27         54 my $callbacked = 0;
125 27         38 my $result;
126             $storage->get_unacked_counts(
127             timeline => $timeline, callback => sub {
128 27     27   56 my ($error, $unacked_counts) = @_;
129 27         135 is($error, undef, 'operation succeed');
130 27         11955 $result = $unacked_counts;
131 27         58 $callbacked = 1;
132 27         81 $unloop->();
133             }
134 27         308 );
135 27         212 $loop->();
136 27         109 ok($callbacked, 'callbacked');
137 27         10788 return %$result;
138             }
139              
140             sub on_statuses {
141 1152     1152 0 6563 my ($storage, $loop, $unloop, $query_ref, $code) = @_;
142 1152         2731 local $Test::Builder::Level = $Test::Builder::Level + 1;
143 1152         5470 $code->(sync_get($storage, $loop, $unloop, %$query_ref));
144             }
145              
146             sub change_and_check {
147 252     252 0 2051 my ($storage, $loop, $unloop, %args) = @_;
148 252         597 my $callbacked = 0;
149 252         745 local $Test::Builder::Level = $Test::Builder::Level + 1;
150 252   100     2276 my $label = "change_and_check " . ($args{label} || "") . ":";
151             my $callback_func = sub {
152 252     252   694 my ($error, $result) = @_;
153 252         3289 is($error, undef, "$label $args{mode} succeed.");
154 252         168287 is($result, $args{exp_change},
155             "$label $args{mode} changed $args{exp_change}");
156 252         101995 $callbacked = 1;
157 252         1087 $unloop->();
158 252         1884 };
159 252 100 100     3024 if($args{mode} eq 'insert' || $args{mode} eq 'update' || $args{mode} eq 'upsert') {
    100 100        
    50          
160 159         1321 $storage->put_statuses(
161             timeline => $args{timeline},
162             mode => $args{mode},
163             statuses => $args{target},
164             callback => $callback_func,
165             );
166 159         608 $loop->();
167             }elsif($args{mode} eq 'delete') {
168 33         165 my $method = "$args{mode}_statuses";
169 33         171 my %method_args = (
170             timeline => $args{timeline},
171             callback => $callback_func,
172             );
173 33 50       228 $method_args{ids} = $args{target} if exists($args{target});
174 33         265 $storage->$method(%method_args);
175 33         136 $loop->();
176             }elsif($args{mode} eq 'ack') {
177 60         207 my $method = "$args{mode}_statuses";
178 60         287 my %method_args = (
179             timeline => $args{timeline},
180             callback => $callback_func,
181             );
182 60 100       277 $method_args{max_id} = $args{target} if exists($args{target});
183 60 100       258 $method_args{max_id} = $args{target_max_id} if exists($args{target_max_id});
184 60 100       272 $method_args{ids} = $args{target_ids} if exists($args{target_ids});
185 60         541 $storage->$method(%method_args);
186             }else {
187 0         0 croak "Invalid mode";
188             }
189             on_statuses $storage, $loop, $unloop, {
190             timeline => $args{timeline}, count => 'all',
191             ack_state => 'acked'
192             }, sub {
193 252     252   632 my $statuses = shift;
194 252         1782 test_status_id_set(
195             $statuses, $args{exp_acked},
196             "$label acked statuses OK"
197             );
198 252         176363 foreach my $s (@$statuses) {
199 2019         662849 ok(acked($s), "$label acked");
200             }
201 252         9027 };
202             on_statuses $storage, $loop, $unloop, {
203             timeline => $args{timeline}, count => 'all',
204             ack_state => 'unacked',
205             }, sub {
206 252     252   613 my $statuses = shift;
207 252         1565 test_status_id_set(
208             $statuses, $args{exp_unacked},
209             "$label unacked statuses OK"
210             );
211 252         173182 foreach my $s (@$statuses) {
212 2144         720629 ok(!acked($s), "$label not acked");
213             }
214 252         63451 };
215             on_statuses $storage, $loop, $unloop, {
216             timeline => $args{timeline}, count => 'all',
217             ack_state => 'any',
218             }, sub {
219 252     252   629 my $statuses = shift;
220 252         969 test_status_id_set(
221 252         666 $statuses, [@{$args{exp_acked}}, @{$args{exp_unacked}}],
  252         2624  
222             "$label statuses in any state OK"
223             );
224 252         77417 };
225             }
226              
227             sub get_and_check_list {
228 174     174 0 533 my ($storage, $loop, $unloop, $get_args, $exp_id_list, $msg) = @_;
229 174         412 local $Test::Builder::Level = $Test::Builder::Level + 1;
230             on_statuses $storage, $loop, $unloop, $get_args, sub {
231 174     174   420 my $statuses = shift;
232 174         645 test_status_id_list $statuses, $exp_id_list, $msg;
233 174         1275 };
234             }
235              
236             sub test_cases_for_ack {
237             ## ** assumption: acked [1..10] (sufficiently old), unacked [11..20]
238 12     12 0 7506 my (%args) = @_;
239 12 100       51 if($args{is_ordered}) {
240             return (
241 4         330 {label => 'max_id', req => {max_id => 14}, exp_count => 4,
242             exp_unacked => [reverse 15..20], exp_acked => [reverse 1..14]},
243             {label => 'max_id with ids < max_id', req => {ids => 12, max_id => 15}, exp_count => 5,
244             exp_unacked => [reverse 16..20], exp_acked => [reverse 1..15]},
245             {label => 'max_id with ids > max_id', req => {ids => [15,17], max_id => 13}, exp_count => 5,
246             exp_unacked => [reverse 14,16,18..20], exp_acked => [reverse 1..13,15,17]},
247             {label => 'max_id with ids = max_id', req => {ids => 15, max_id => 15}, exp_count => 5,
248             exp_unacked => [reverse 16..20], exp_acked => [reverse 1..15]},
249             {label => 'max_id with ids all cases', req => {ids => [4,14,18,20,24], max_id => 18}, exp_count => 9,
250             exp_unacked => [19], exp_acked => [reverse 1..18,20]}
251             );
252             }else {
253             return (
254 8         755 {label => "no body", req => undef, exp_count => 10,
255             exp_unacked => [], exp_acked => [reverse 1..20]},
256             {label => "empty", req => {}, exp_count => 10,
257             exp_unacked => [], exp_acked => [reverse 1..20]},
258             {label => 'both null', req => {ids => undef, max_id => undef}, exp_count => 10,
259             exp_unacked => [], exp_acked => [reverse 1..20]},
260             {label => 'empty ids', req => {ids => []}, exp_count => 0,
261             exp_unacked => [reverse 11..20], exp_acked => [reverse 1..10]},
262             {label => 'empty ids with undef max_id', req => {ids => [], max_id => undef}, exp_count => 0,
263             exp_unacked => [reverse 11..20], exp_acked => [reverse 1..10]},
264             {label => 'single ids', req => {ids => 15}, exp_count => 1,
265             exp_unacked => [reverse 11..14,16..20], exp_acked => [reverse 1..10,15]},
266             {label => 'multi ids', req => {ids => [13,14,15]}, exp_count => 3,
267             exp_unacked => [reverse 11,12,16..20], exp_acked => [reverse 1..10,13,14,15]},
268             {label => 'multi ids with unknown id', req => {ids => [19..23]}, exp_count => 2,
269             exp_unacked => [reverse 11..18], exp_acked => [reverse 1..10,19,20]},
270             {label => 'multi ids with acked id', req => {ids => [8..14]}, exp_count => 4,
271             exp_unacked => [reverse 15..20], exp_acked => [reverse 1..14]},
272             {label => 'multi ids (all unknown)', req => {ids => [-1,-4,21,24]}, exp_count => 0,
273             exp_unacked => [reverse 11..20], exp_acked => [reverse 1..10]},
274             {label => 'max_id to unknown id', req => {max_id => 23}, exp_count => 0,
275             exp_unacked => [reverse 11..20], exp_acked => [reverse 1..10]},
276             {label => 'max_id to acked id', req => {max_id => 7}, exp_count => 0,
277             exp_unacked => [reverse 11..20], exp_acked => [reverse 1..10]},
278             );
279             }
280             }
281              
282             sub check_contains {
283 36     36 0 129 my ($storage, $loop, $unloop, $input, $exp_out, $msg) = @_;
284 36         86 local $Test::Builder::Level = $Test::Builder::Level + 1;
285 36         125 my %args = %$input;
286 36         64 my $callbacked = 0;
287             $args{callback} = sub {
288 36     36   199 is_deeply \@_, $exp_out, $msg;
289 36         44526 $callbacked++;
290 36         139 $unloop->();
291 36         205 };
292 36         200 $storage->contains(%args);
293 36         2724 $loop->();
294 36         141 is $callbacked, 1, "callbacked once";
295             }
296              
297              
298             sub test_storage_common {
299 3     3 1 673 my ($storage, $loop, $unloop) = @_;
300 3         23 note('-------- test_storage_common');
301 3   50 909   779 $loop ||= sub {};
  909         4894  
302 3   50 951   26 $unloop ||= sub {};
  951         3150  
303 3         6 my $callbacked = 0;
304 3         20 isa_ok($storage, 'BusyBird::StatusStorage');
305 3         1724 can_ok($storage, 'get_unacked_counts', map { "${_}_statuses" } qw(ack get put delete));
  12         43  
306 3         1819 note("--- clear the timelines");
307 3         422 foreach my $tl ('_test_tl1', "_test_tl2") {
308 6         2457 $callbacked = 0;
309             $storage->delete_statuses(
310             timeline => $tl,
311             ids => undef,
312             callback => sub {
313 6     6   12 $callbacked = 1;
314 6         20 $unloop->();
315             }
316 6         71 );
317 6         29 $loop->();
318 6         32 ok($callbacked, "callbacked");
319 6         2993 is_deeply(
320             { sync_get_unacked_counts($storage, $loop, $unloop, $tl) },
321             { total => 0 },
322             "$tl is empty"
323             );
324             }
325            
326 3         4437 note("--- put_statuses (insert), single");
327 3         545 $callbacked = 0;
328             $storage->put_statuses(
329             timeline => '_test_tl1',
330             mode => 'insert',
331             statuses => status(1),
332             callback => sub {
333 3     3   10 my ($error, $num) = @_;
334 3         19 is($error, undef, 'put_statuses succeed.');
335 3         1815 is($num, 1, 'put 1 status');
336 3         1175 $callbacked = 1;
337 3         15 $unloop->();
338             }
339 3         22 );
340 3         29 $loop->();
341 3         13 ok($callbacked, "callbacked");
342 3         1137 is_deeply(
343             { sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
344             { total => 1, 0 => 1 },
345             '1 unacked status'
346             );
347 3         2159 note('--- put_statuses (insert), multiple');
348 3         554 $callbacked = 0;
349 12         83 $storage->put_statuses(
350             timeline => '_test_tl1',
351             mode => 'insert',
352             statuses => [map { status($_) } 2..5],
353             callback => sub {
354 3     3   15 my ($error, $num) = @_;
355 3         25 is($error, undef, 'put_statuses succeed');
356 3         2145 is($num, 4, 'put 4 statuses');
357 3         1352 $callbacked = 1;
358 3         17 $unloop->();
359             }
360 3         14 );
361 3         42 $loop->();
362 3         101 ok($callbacked, "callbacked");
363 3         1020 is_deeply(
364             { sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
365             { total => 5, 0 => 5 },
366             '5 unacked status'
367             );
368              
369 3         1960 note('--- get_statuses: any, all');
370 3         420 $callbacked = 0;
371             $storage->get_statuses(
372             timeline => '_test_tl1',
373             count => 'all',
374             callback => sub {
375 3     3   7 my ($error, $statuses) = @_;
376 3         26 is($error, undef, "get_statuses succeed");
377 3         1910 test_status_id_set($statuses, [1..5], "1..5 statuses");
378 3         2074 foreach my $s (@$statuses) {
379 7     7   15777 no autovivification;
  7         15  
  7         48  
380 15         7228 ok(!$s->{busybird}{acked_at}, "status is not acked");
381             }
382 3         1644 $callbacked = 1;
383 3         16 $unloop->();
384             }
385 3         40 );
386 3         33 $loop->();
387 3         12 ok($callbacked, "callbacked");
388              
389 3         1383 note('--- ack_statuses: all');
390 3         577 $callbacked = 0;
391             $storage->ack_statuses(
392             timeline => '_test_tl1',
393             callback => sub {
394 3     3   10 my ($error, $num) = @_;
395 3         20 is($error, undef, "ack_statuses succeed");
396 3         1673 is($num, 5, "5 statuses acked.");
397 3         1171 $callbacked = 1;
398 3         15 $unloop->();
399             }
400 3         45 );
401 3         350 $loop->();
402 3         17 ok($callbacked, "callbacked");
403 3         1287 is_deeply(
404             { sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1') },
405             { total => 0 },
406             "all acked"
407             );
408             on_statuses $storage, $loop, $unloop, {
409             timeline => '_test_tl1', count => 'all'
410             }, sub {
411 3     3   9 my $statuses = shift;
412 3         18 is(int(@$statuses), 5, "5 statueses");
413 3         1037 foreach my $s (@$statuses) {
414 7     7   1939 no autovivification;
  7         16  
  7         24  
415 15         4212 ok($s->{busybird}{acked_at}, 'acked');
416             }
417 3         1948 };
418              
419 3         1419 note('--- delete_statuses (single deletion)');
420 3         708 $callbacked = 0;
421             $storage->delete_statuses(
422             timeline => '_test_tl1',
423             ids => 3,
424             callback => sub {
425 3     3   12 my ($error, $num) = @_;
426 3         24 is($error, undef, "operation succeed.");
427 3         1887 is($num, 1, "1 deletion");
428 3         1118 $callbacked = 1;
429 3         15 $unloop->();
430             }
431 3         46 );
432 3         26 $loop->();
433 3         15 ok($callbacked, "callbacked");
434             on_statuses $storage, $loop, $unloop, {
435             timeline => '_test_tl1', count => 'all'
436             }, sub {
437 3     3   8 my $statuses = shift;
438 3         19 test_status_id_set($statuses, [1,2,4,5], "ID=3 is deleted");
439 3         1150 };
440              
441 3         1673 note('--- delete_statuses (multiple deletion)');
442 3         473 $callbacked = 0;
443             $storage->delete_statuses(
444             timeline => '_test_tl1',
445             ids => [1, 4],
446             callback => sub {
447 3     3   9 my ($error, $num) = @_;
448 3         21 is($error, undef, 'operation succeed');
449 3         1606 is($num, 2, "2 statuses deleted");
450 3         947 $callbacked = 1;
451 3         12 $unloop->();
452             }
453 3         38 );
454 3         22 $loop->();
455 3         11 ok($callbacked, "callbacked");
456             on_statuses $storage, $loop, $unloop, {
457             timeline => '_test_tl1', count => 'all'
458             }, sub {
459 3     3   6 my $statuses = shift;
460 3         14 test_status_id_set($statuses, [2,5], "ID=1,4 are deleted");
461 3         863 };
462              
463 3         1689 note('--- delete_statuses (all deletion)');
464 3         401 $callbacked = 0;
465             $storage->delete_statuses(
466             timeline => '_test_tl1',
467             ids => undef,
468             callback => sub {
469 3     3   8 my ($error, $num) = @_;
470 3         23 is($error, undef, 'operation succeed');
471 3         1547 is($num, 2, "2 statuses deleted");
472 3         983 $callbacked = 1;
473 3         12 $unloop->();
474             }
475 3         36 );
476 3         20 $loop->();
477 3         12 ok($callbacked, "callbacked");
478             on_statuses $storage, $loop, $unloop, {
479             timeline => '_test_tl1', count => 'all'
480             }, sub {
481 3     3   8 my $statuses = shift;
482 3         14 test_status_id_set($statuses, [], "ID=2,5 are deleted. now empty");
483 3         931 };
484              
485 3         1981 note('--- put_statuses (insert): insert duplicate IDs');
486 27         54 change_and_check(
487             $storage, $loop, $unloop, timeline => '_test_tl1',
488 3         904 mode => 'insert', target => [map { status $_ } (1,2,3,2,1,1,4,5,3)],
489             exp_change => 5,
490             exp_unacked => [1..5], exp_acked => []
491             );
492 3         2172 note('--- ack_statuses: with max_id');
493 3         427 $callbacked = 0;
494             $storage->ack_statuses(
495             timeline => '_test_tl1', max_id => 3, callback => sub {
496 3     3   10 my ($error, $ack_count) = @_;
497 3         20 is($error, undef, "ack_statuses succeed");
498 3         1696 cmp_ok($ack_count, ">=", 1, "$ack_count (>= 1) acked.");
499 3         872 $callbacked = 1;
500 3         13 $unloop->();
501             }
502 3         43 );
503 3         240 $loop->();
504 3         15 ok($callbacked, 'callbacked');
505             on_statuses $storage, $loop, $unloop, {
506             timeline => '_test_tl1', max_id => 3, count => 1
507             }, sub {
508 3     3   6 my ($statuses) = @_;
509 3         15 test_status_id_set $statuses, [3], 'get status ID = 3';
510 3         1570 ok(acked($statuses->[0]), 'at least status ID = 3 is acked.');
511 3         943 };
512 3         907 note('--- ack_statuses: ack all with max_id => undef');
513 3         431 $callbacked = 0;
514             $storage->ack_statuses(
515             timeline => '_test_tl1', max_id => undef, callback => sub {
516 3     3   12 my ($error, $ack_count) = @_;
517 3         29 is($error, undef, 'ack_statuses succeed');
518 3         1922 $callbacked = 1;
519 3         11 $unloop->();
520             }
521 3         36 );
522 3         299 $loop->();
523 3         16 ok($callbacked, "callbacked");
524             on_statuses $storage, $loop, $unloop, {
525             timeline => '_test_tl1', count => 'all',
526             }, sub {
527 3     3   10 my $statuses = shift;
528 3         18 test_status_id_set($statuses, [1..5], "5 statuses");
529 3         2289 foreach my $s (@$statuses) {
530 15         5945 ok(acked($s), "Status ID = $s->{id} is acked");
531             }
532 3         1249 };
533 3         1362 note('--- put (insert): try to insert existent status');
534 3         495 change_and_check(
535             $storage, $loop, $unloop, timeline => '_test_tl1',
536             mode => 'insert', target => status(3), exp_change => 0,
537             exp_unacked => [], exp_acked => [1..5]
538             );
539 3         2041 note('--- put (update): change to unacked');
540 6         53 change_and_check(
541             $storage, $loop, $unloop, timeline => '_test_tl1',
542 3         617 mode => 'update', target => [map {status($_)} (2,4)], exp_change => 2,
543             exp_unacked => [2,4], exp_acked => [1,3,5]
544             );
545 3         1967 note('--- put (update): change to unacked');
546 6         20 change_and_check(
547             $storage, $loop, $unloop, timeline => '_test_tl1',
548 3         426 mode => 'update', target => [map { status($_) } (3,5)],
549             exp_change => 2, exp_unacked => [2,3,4,5], exp_acked => [1]
550             );
551 3         2027 is_deeply(
552             {sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
553             {total => 4, 0 => 4}, '4 unacked statuses'
554             );
555 3         1924 note('--- put (update): change level');
556 15 100       66 change_and_check(
557             $storage, $loop, $unloop, timeline => '_test_tl1',
558             mode => 'update',
559 3         449 target => [map { status($_, ($_ % 2 + 1), $_ == 1 ? nowstring() : undef) } (1..5)],
560             exp_change => 5, exp_unacked => [2,3,4,5], exp_acked => [1]
561             );
562 3         1686 is_deeply(
563             {sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
564             {total => 4, 1 => 2, 2 => 2}, "4 unacked statuses in 2 levels"
565             );
566 3         1679 note('--- put (upsert): acked statuses');
567 12         31 change_and_check(
568             $storage, $loop, $unloop, timeline => '_test_tl1',
569 3         501 mode => 'upsert', target => [map { status($_, 7, nowstring()) } (4..7)],
570             exp_change => 4, exp_unacked => [2,3], exp_acked => [1,4..7]
571             );
572 3         1829 note('--- get and put(update): back to unacked');
573             on_statuses $storage, $loop, $unloop, {
574             timeline => '_test_tl1', count => 'all', ack_state => 'acked'
575             }, sub {
576 3     3   9 my $statuses = shift;
577 3         29 delete $_->{busybird}{acked_at} foreach @$statuses;
578 3         22 change_and_check(
579             $storage, $loop, $unloop, timeline => '_test_tl1',
580             mode => 'update', target => $statuses,
581             exp_change => 5, exp_unacked => [1..7], exp_acked => []
582             );
583 3         427 };
584 3         2379 is_deeply(
585             {sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl1')},
586             {total => 7, 1 => 1, 2 => 2, 7 => 4}, "3 levels"
587             );
588              
589 3         2113 note('--- put(insert): to another timeline');
590 30         70 change_and_check(
591             $storage, $loop, $unloop, timeline => '_test_tl2',
592 3         427 mode => 'insert', target => [map { status($_) } (1..10)],
593             exp_change => 10, exp_unacked => [1..10], exp_acked => []
594             );
595 3         1786 is_deeply(
596             {sync_get_unacked_counts($storage, $loop, $unloop, '_test_tl2')},
597             {total => 10, 0 => 10}, '10 unacked statuses'
598             );
599             ## change_and_check(
600             ## $storage, $loop, $unloop, timeline => '_test_tl2',
601             ## mode => 'ack', target => [1..5],
602             ## exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
603             ## );
604 15         33 change_and_check(
605             $storage, $loop, $unloop, timeline => '_test_tl2',
606 3         1561 mode => 'update', target => [map {status($_, undef, nowstring())} (1..5)],
607             exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
608             );
609 3         1956 note('--- get: single, any state');
610 3         573 foreach my $id (1..10) {
611             on_statuses $storage, $loop, $unloop, {
612             timeline => '_test_tl2', count => 1, max_id => $id
613             }, sub {
614 30     30   61 my $statuses = shift;
615 30         119 is(int(@$statuses), 1, "get 1 status");
616 30         10825 is($statuses->[0]{id}, $id, "... and its ID is $id");
617 30         9916 };
618             }
619 3         1098 note('--- get: single, specific state');
620 3         427 foreach my $id (1..10) {
621 30 100       10838 my $correct_state = ($id <= 5) ? 'acked' : 'unacked';
622 30 100       112 my $wrong_state = $correct_state eq 'acked' ? 'unacked' : 'acked';
623             on_statuses $storage, $loop, $unloop, {
624             timeline => '_test_tl2', count => 1, max_id => $id,
625             ack_state => $correct_state,
626             }, sub {
627 30     30   68 my $statuses = shift;
628 30         138 is(int(@$statuses), 1, "get 1 status");
629 30         10615 is($statuses->[0]{id}, $id, "... and its ID is $id");
630 30         322 };
631 30         11690 foreach my $count ('all', 1, 10) {
632             on_statuses $storage, $loop, $unloop, {
633             timeline => '_test_tl2', count => $count, max_id => $id,
634             ack_state => $wrong_state
635             }, sub {
636 90     90   181 my $statuses = shift;
637 90         692 is(int(@$statuses), 0,
638             "no status returned when status specified" .
639             " max_id is not the correct ack_state".
640             " even when count = $count");
641 90         24474 };
642             }
643             }
644 3         1269 note('--- contains');
645 3         527 foreach my $case (
646             { label => 'single status (in)',
647             input => {query => status(3)}, exp => [undef, [status(3)], []]},
648             { label => 'single status (out)',
649             input => {query => status(90)}, exp => [undef, [], [status(90)]]},
650             { label => 'single id (in)', input => {query => 5}, exp => [undef, [5], []]},
651             { label => 'single id (out)', input => {query => 8}, exp => [undef, [], [8]]},
652             { label => 'mixed array',
653             input => {query => [ 1, status(10), status(5), 10, 10, 3, 2, status(2), 4, 3, status(0), 8 ]},
654             exp => [undef, [1, status(5), 3, 2, status(2), 4, 3], [status(10), 10, 10, status(0), 8]]},
655             { label => 'empty array', input => {query => []}, exp => [undef, [], []]},
656             { label => 'ID-less status', input => {query => {text => 'hoge'}}, exp => [undef, [], [{text => 'hoge'}]] },
657             { label => 'mixed ID-less statuses',
658             input => {query => [ {text => "foo"}, status(4), 11, {text => 'bar'}, status(9), 3 ]},
659             exp => [undef, [status(4), 3], [{text => "foo"}, 11, {text => 'bar'}, status(9)]]},
660             ) {
661 24         7938 my %args = (%{$case->{input}}, timeline => '_test_tl1');
  24         142  
662 24         102 check_contains $storage, $loop, $unloop, \%args, $case->{exp}, $case->{label};
663             }
664 3         1256 note('--- timeline is independent of each other');
665             on_statuses $storage, $loop, $unloop, {
666             timeline => "_test_tl1", count => "all"
667             }, sub {
668 3     3   8 my $statuses = shift;
669 3         16 test_status_id_set($statuses, [1..7], "7 statuses in _test_tl1");
670 3         525 };
671             on_statuses $storage, $loop, $unloop, {
672             timeline => '_test_tl2', count => "all",
673             }, sub {
674 3     3   9 my $statuses = shift;
675 3         21 test_status_id_set($statuses, [1..10], "10 statuses in _test_tl2");
676 3         1848 };
677 3         2141 check_contains($storage, $loop, $unloop,
678             {timeline => '_test_tl2', query => [5, 7, 9, 11]}, [undef, [5,7,9], [11]],
679             'contains() for _test_tl2 timeline');
680 3         1176 note('--- access to non-existent statuses');
681 3         467 foreach my $test_set (
  15         36  
682             {mode => 'update', target => [map { status($_) } (11..15) ]},
683             {mode => 'delete', target => [11..15]},
684             ) {
685 6   50     2301 my $label = "mode $test_set->{mode} " . ($test_set->{label} || "");
686 6         32 my %target_args = %$test_set;
687 6         21 delete @target_args{"mode", "label"};
688 6         47 change_and_check(
689             $storage, $loop, $unloop, timeline => '_test_tl2',
690             mode => $test_set->{mode}, label => $label, %target_args,
691             exp_change => 0, exp_unacked => [6..10],
692             exp_acked => [1..5]
693             );
694             }
695             on_statuses $storage, $loop, $unloop, {
696             timeline => '_test_tl2', count => 'all', max_id => 15,
697             }, sub {
698 3     3   8 my $statuses = shift;
699 3         15 is(int(@$statuses), 0, "get max_id=15 returns empty");
700 3         2149 };
701 3         1140 note('--- access to non-existent timeline');
702 3         485 foreach my $mode (qw(update delete ack)) {
703 9         3808 my $timeline = '_this_timeline_ probably does not exist';
704 9 100       47 my $target = $mode eq 'update'
705             ? status(1) : 1;
706 9         67 change_and_check(
707             $storage, $loop, $unloop, timeline => $timeline,
708             mode => $mode, target => $target, lable => "mode $mode",
709             exp_change => 0, exp_unacked => [], exp_acked => []
710             );
711             }
712 3         1862 check_contains($storage, $loop, $unloop,
713             {timeline => "_non_existent timeline", query => [0..20]},
714             [undef, [], [0..20]],
715             'contains() for non-existent timeline returns all queries as not_contained');
716 3         1084 note('--- changes done to obtained statuses do not affect storage.');
717             on_statuses $storage, $loop, $unloop, {
718             timeline => '_test_tl2', count => 'all'
719             }, sub {
720 3     3   9 my $statuses = shift;
721 3         15 is(int(@$statuses), 10, "10 statuses");
722 3         1163 $_->{id} = 100 foreach @$statuses;
723 3         473 };
724             on_statuses $storage, $loop, $unloop, {
725             timeline => '_test_tl2', count => 'all'
726             }, sub {
727 3     3   8 my $statuses = shift;
728 3         20 test_status_id_set($statuses, [1..10], "ID set in storage is not changed.");
729 3         280 };
730             {
731 3         2326 note('--- changes done to inserted/updated statuses do not affect storage.');
  3         17  
732 3         530 my @upserted = map { status $_ } 1..20;
  60         124  
733 3         34 change_and_check(
734             $storage, $loop, $unloop, timeline => '_test_tl2',
735             mode => 'upsert', target => \@upserted, exp_change => 20,
736             exp_acked => [], exp_unacked => [1..20]
737             );
738 3         2237 $_->{id} = 100 foreach @upserted;
739             on_statuses $storage, $loop, $unloop, {
740             timeline => '_test_tl2', count => 'all'
741             }, sub {
742 3     3   9 my $statuses = shift;
743 3         23 test_status_id_set($statuses, [1..20], 'ID set in storage is not changed');
744 3         38 };
745             }
746              
747             {
748 3         2199 note('--- -- test acks with max_id (unordered)');
  3         14  
749 3         394 $callbacked = 0;
750             $storage->delete_statuses(timeline => '_test_acks', ids => undef, callback => sub {
751 3     3   8 $callbacked = 1;
752 3         10 $unloop->();
753 3         30 });
754 3         13 $loop->();
755 3         15 ok($callbacked, 'callbacked');
756 90         177 change_and_check(
757             $storage, $loop, $unloop, timeline => '_test_acks',
758 3         1073 mode => 'insert', target => [map {status($_)} 1..30], exp_change => 30,
759             exp_acked => [], exp_unacked => [1..30]
760             );
761 3         2517 note('--- ack: ids and max_id (max_id < ids)');
762 3         466 $callbacked = 0;
763             $storage->ack_statuses(timeline => '_test_acks', ids => [25..28], max_id => 4, callback => sub {
764 3     3   16 my ($error, $count) = @_;
765 3         8 $callbacked = 1;
766 3         22 is($error, undef, "ack_statuses succeed");
767 3         1899 cmp_ok($count, ">=", 5, 'at least 5 statuses acked.');
768 3         1127 $unloop->();
769 3         45 });
770 3         237 $loop->();
771 3         16 ok($callbacked, "callbacked");
772 3         1018 change_and_check(
773             $storage, $loop, $unloop, timeline => '_test_acks',
774             mode => 'delete', target => undef, exp_change => 30, exp_acked => [], exp_unacked => []
775             );
776 30         63 change_and_check(
777             $storage, $loop, $unloop, timeline => '_test_acks',
778 3         12598 mode => 'insert', target => [map {status($_)} 31..40], exp_change => 10, exp_acked => [], exp_unacked => [31..40]
779             );
780 3         1979 note('--- ack: ids and max_id (max_id > ids)');
781 3         451 $callbacked = 0;
782             $storage->ack_statuses(timeline => '_test_acks', ids => 32, max_id => 39, callback => sub {
783 3     3   15 my ($error, $count) = @_;
784 3         8 $callbacked = 1;
785 3         20 is($error, undef, 'ack_statuses succeed');
786 3         4835 cmp_ok($count, ">=", 2, "at least 2 statuses acked");
787 3         1199 $unloop->();
788 3         60 });
789 3         268 $loop->();
790 3         17 ok($callbacked, "callbacked");
791 3         1085 change_and_check(
792             $storage, $loop, $unloop, timeline => '_test_acks',
793             mode => 'delete', target => undef, exp_change => 10, exp_acked => [], exp_unacked => []
794             );
795             }
796              
797             {
798 3         1688 note('--- -- acks with various argument cases (unordered)');
  3         17  
799 3         301 foreach my $case (test_cases_for_ack(is_ordered => 0)) {
800 36         23228 my $callbacked = 0;
801 36 100       169 next if not defined $case->{req};
802 33         212 note("--- case: $case->{label}");
803             $storage->delete_statuses(timeline => '_test_acks', ids => undef, callback => sub {
804 33     33   83 my ($error, $count) = @_;
805 33         185 is($error, undef, "delete succeed");
806 33         16466 $callbacked = 1;
807 33         120 $unloop->();
808 33         5530 });
809 33         222 $loop->();
810 33         583 ok($callbacked, 'callbacked');
811 33         12425 my $already_acked_at = nowstring();
812 330         719 change_and_check(
813             $storage, $loop, $unloop, timeline => '_test_acks', mode => 'insert',
814 33         23696 target => [(map {status($_, 0, $already_acked_at)} 1..10), (map {status($_)} 11..20)],
  330         683  
815             exp_change => 20, exp_acked => [1..10], exp_unacked => [11..20]
816             );
817 33         26654 my %target_args = ();
818 33 100       292 $target_args{target_ids} = $case->{req}{ids} if exists $case->{req}{ids};
819 33 100       249 $target_args{target_max_id} = $case->{req}{max_id} if exists $case->{req}{max_id};
820 33         222 change_and_check(
821             $storage, $loop, $unloop, timeline => '_test_acks', mode => 'ack',
822             %target_args, exp_change => $case->{exp_count}, exp_acked => $case->{exp_acked}, exp_unacked => $case->{exp_unacked}
823             );
824             }
825             }
826              
827             {
828 3         2441 note('--- -- -- Unicode timeline name and Unicode status ID');
  3         20  
829 3         253 foreach my $timeline_name ("_test_ascii", '_test_ゆにこーど') {
830 6         2010 note(encode_utf8("--- -- timeline: $timeline_name"));
831             $storage->delete_statuses(timeline => $timeline_name, ids => undef, callback => sub {
832 6     6   15 my $e = shift;
833 6         28 is($e, undef, "initial delete");
834 6         1919 $unloop->();
835 6         587 });
836 6         32 $loop->();
837 6         20 my @statuses = map { status($_) } 0..1;
  12         39  
838 6         25 $statuses[1]{id} = 'いち';
839 6         23 $statuses[0]{text} = 'テキスト ゼロ';
840 6         19 $statuses[1]{text} = 'テキスト いち';
841 6         41 change_and_check(
842             $storage, $loop, $unloop, timeline => $timeline_name, mode => 'insert', target => \@statuses,
843             exp_change => 2, exp_acked => [], exp_unacked => [qw(0 いち)]
844             );
845 6         3928 check_contains($storage, $loop, $unloop,
846             {timeline => $timeline_name, query => [$statuses[1], 'に', 'いち', 0]},
847             [undef, [$statuses[1], 'いち', 0], ['に']],
848             encode_utf8("contains() works fine with Unicode timeline $timeline_name and Unicode status IDs"));
849             $storage->get_unacked_counts(timeline => $timeline_name, callback => sub {
850 6     6   20 my ($e, $unacked_counts) = @_;
851 6         47 is($e, undef, "get unacked counts succeed");
852 6         2279 is_deeply($unacked_counts, {total => 2, 0 => 2}, "unacked counts OK");
853 6         3677 $unloop->();
854 6         2339 });
855 6         55 $loop->();
856 6         58 change_and_check(
857             $storage, $loop, $unloop, timeline => $timeline_name, mode => 'ack', target_ids => $statuses[1]{id},
858             exp_change => 1, exp_acked => ["いち"], exp_unacked => [0]
859             );
860 6         3598 foreach my $status (@statuses) {
861 12         2236 my $got_statuses = sync_get(
862             $storage, $loop, $unloop,
863             timeline => $timeline_name, count => 1, max_id => $status->{id}
864             );
865 12         121 test_status_id_list($got_statuses, [$status->{id}], encode_utf8("status ID $status->{id} OK"));
866 12         7325 is($got_statuses->[0]{text}, $status->{text}, encode_utf8("status text '$status->{text}' OK"));
867 12 100       4074 if($got_statuses->[0]{id} eq "0") {
868 6         45 ok(!$got_statuses->[0]{busybird}{acked_at}, "status 0 is not acked");
869             }else {
870 6         36 ok($got_statuses->[0]{busybird}{acked_at}, "status 1 is acked");
871             }
872             }
873 6         2075 $statuses[1]{busybird}{level} = 5;
874 6         48 change_and_check(
875             $storage, $loop, $unloop, timeline => $timeline_name, mode => "update", target => $statuses[1],
876             exp_change => 1, exp_acked => [], exp_unacked => [0, "いち"]
877             );
878             $storage->get_unacked_counts(timeline => $timeline_name, callback => sub {
879 6     6   14 my ($e, $unacked_counts) = @_;
880 6         38 is($e, undef, "get unacked counts succeed");
881 6         2552 is_deeply($unacked_counts, {total => 2, 0 => 1, 5 => 1}, "unacked counts OK");
882 6         3515 $unloop->();
883 6         3627 });
884 6         52 $loop->();
885 12         64 change_and_check(
886 6         26 $storage, $loop, $unloop, timeline => $timeline_name, mode => "delete", target => [map {$_->{id}} @statuses],
887             exp_change => 2, exp_acked => [], exp_unacked => []
888             );
889             }
890             }
891              
892 3         1878 note('--- clean up');
893 3         299 foreach my $tl ('_test_tl1', '_test_tl2') {
894 6         955 $callbacked = 0;
895             $storage->delete_statuses(timeline => $tl, ids => undef, callback => sub {
896 6     6   14 my $error= shift;
897 6         34 is($error, undef, "operation succeed");
898 6         2673 $callbacked = 1;
899 6         19 $unloop->();
900 6         62 });
901 6         34 $loop->();
902 6         32 ok($callbacked, "callbacked");
903             }
904             }
905              
906             sub test_storage_ordered {
907 3     3 1 975 my ($storage, $loop, $unloop) = @_;
908 3   50 549   34 $loop ||= sub {};
  549         1865  
909 3   50 567   23 $unloop ||= sub {};
  567         1948  
910 3         80 note('-------- test_storage_ordered');
911 3         296 note('--- clear timeline');
912 3         227 my $callbacked = 0;
913 3         13 foreach my $tl (qw(_test_tl3 _test_tl4 _test_tl5)) {
914 9         1749 $callbacked = 0;
915             $storage->delete_statuses(timeline => $tl, ids => undef, callback => sub {
916 9     9   16 my $error = shift;
917 9         34 is($error, undef, "operation succeed");
918 9         3121 $callbacked = 1;
919 9         22 $unloop->();
920 9         82 });
921 9         37 $loop->();
922 9         27 ok($callbacked, "callbacked");
923             }
924 3         903 note('--- acked_at and created_at are preserved');
925 3         337 foreach my $case (
926             {label => "both unset", created_at => undef, acked_at => undef},
927             {label => "only created_at set", created_at => 'Mon Jul 01 22:11:41 +0900 2013',
928             acked_at => undef},
929             {label => "only acked_at set", created_at => undef,
930             acked_at => "Wed Apr 17 04:23:29 -0500 2013"},
931             {label => 'both set', created_at => 'Fri Oct 12 00:36:44 +0000 2012',
932             acked_at => 'Thu Oct 25 13:10:00 +0200 2012'},
933             ) {
934 12         4833 note("--- -- case: $case->{label}");
935 12         1193 $callbacked = 0;
936 12         50 my $status = status(1);
937 12         36 $status->{created_at} = $case->{created_at};
938 12         40 $status->{busybird}{acked_at} = $case->{acked_at};
939             $storage->put_statuses(
940             timeline => "_test_tl3", mode => 'insert', statuses => $status,
941             callback => sub {
942 12     12   40 my ($error, $count) = @_;
943 12         84 is($error, undef, "put succeed");
944 12         6091 is($count, 1, "1 inserted");
945 12         3781 $callbacked = 1;
946 12         46 $unloop->();
947             }
948 12         129 );
949 12         91 $loop->();
950 12         70 ok($callbacked, "callbacked");
951             on_statuses $storage, $loop, $unloop, {timeline => '_test_tl3', count => 'all'}, sub {
952 12     12   30 my $statuses = shift;
953 12         56 is(scalar(@$statuses), 1, "1 status obtained");
954 12         3928 is($statuses->[0]{created_at}, $case->{created_at}, "created_at is preserved");
955 12         4019 is($statuses->[0]{busybird}{acked_at}, $case->{acked_at}, "acked_at is preserved");
956 12         3821 };
957 12         4027 change_and_check(
958             $storage, $loop, $unloop, timeline => '_test_tl3',
959             mode => 'delete', target => undef, exp_change => 1,
960             exp_unacked => [], exp_acked => []
961             );
962             }
963 3         1525 note('--- populate timeline');
964 90         184 change_and_check(
965             $storage, $loop, $unloop, timeline => '_test_tl3',
966 3         405 mode => 'insert', target => [map {status $_} (1..30)],
967             label => 'first insert',
968             exp_change => 30, exp_unacked => [1..30], exp_acked => []
969             );
970 3         2267 change_and_check(
971             $storage, $loop, $unloop, timeline => '_test_tl3',
972             mode => 'ack', target => undef, label => 'ack all',
973             exp_change => 30, exp_unacked => [], exp_acked => [1..30]
974             );
975 90         159 change_and_check(
976             $storage, $loop, $unloop, timeline => '_test_tl3',
977 3         2359 mode => 'insert', target => [map {status $_} (31..60)],
978             label => "another insert", exp_change => 30,
979             exp_unacked => [31..60], exp_acked => [1..30]
980             );
981 3         2897 my %base = (timeline => '_test_tl3');
982              
983 3         37 get_and_check_list(
984             $storage, $loop, $unloop, {%base, count => 'all'}, [reverse 1..60],
985             'get: no max_id, any state, all'
986             );
987 3         3436 get_and_check_list(
988             $storage, $loop, $unloop, {%base, count => 20}, [reverse 41..60],
989             'get: no max_id, any state, partial'
990             );
991 3         2728 get_and_check_list(
992             $storage, $loop, $unloop, {%base, count => 40}, [reverse 21..60],
993             'get: no max_id, any state, both states'
994             );
995 3         2373 get_and_check_list(
996             $storage, $loop, $unloop, {%base, count => 120}, [reverse 1..60],
997             'get: no max_id, any state, count larger than the size'
998             );
999              
1000 3         3010 get_and_check_list(
1001             $storage, $loop, $unloop,
1002             {%base, ack_state => 'unacked', count => 'all'},
1003             [reverse 31..60],
1004             'get: no max_id unacked, all'
1005             );
1006 3         2604 get_and_check_list(
1007             $storage, $loop, $unloop,
1008             {%base, ack_state => 'unacked', count => 15},
1009             [reverse 46..60 ],
1010             'get: no max_id, unacked, partial'
1011             );
1012 3         1901 get_and_check_list(
1013             $storage, $loop, $unloop,
1014             {%base, ack_state => 'unacked', count => 50},
1015             [reverse 31..60],
1016             'get: no max_id, unacked, larger than the unacked size'
1017             );
1018              
1019 3         2296 get_and_check_list(
1020             $storage, $loop, $unloop,
1021             {%base, ack_state => 'acked', count => 'all'},
1022             [reverse 1..30],
1023             'get: no max_id, acked, all'
1024             );
1025 3         2148 get_and_check_list(
1026             $storage, $loop, $unloop,
1027             {%base, ack_state => 'acked', count => 25},
1028             [reverse 6..30],
1029             'get: no max_id, acked, partial'
1030             );
1031 3         2747 get_and_check_list(
1032             $storage, $loop, $unloop,
1033             {%base, ack_state => 'acked', count => 70},
1034             [reverse 1..30],
1035             'get: no max_id, acked, larger than the acked size'
1036             );
1037            
1038 3         2607 get_and_check_list(
1039             $storage, $loop, $unloop,
1040             {%base, ack_state => 'any', max_id => 40, count => 'all'},
1041             [reverse 1..40],
1042             'get: max_id in unacked, any state, all'
1043             );
1044 3         2660 get_and_check_list(
1045             $storage, $loop, $unloop,
1046             {%base, ack_state => 'any', max_id => 20, count => 'all'},
1047             [reverse 1..20],
1048             'get: max_id in acked, any state, all'
1049             );
1050 3         2088 get_and_check_list(
1051             $storage, $loop, $unloop,
1052             {%base, ack_state => 'any', max_id => 70, count => 'all'},
1053             [],
1054             'get: non-existent max_id, any state, all'
1055             );
1056              
1057 3         1816 get_and_check_list(
1058             $storage, $loop, $unloop,
1059             {%base, ack_state => 'any', max_id => 50, count => 10},
1060             [reverse 41..50],
1061             'get: max_id in unacked, any state, count inside unacked zone'
1062             );
1063 3         1727 get_and_check_list(
1064             $storage, $loop, $unloop,
1065             {%base, ack_state => 'any', max_id => 50, count => 40},
1066             [reverse 11..50],
1067             'get: max_id in unacked, any state, count to acked zone'
1068             );
1069 3         2912 get_and_check_list(
1070             $storage, $loop, $unloop,
1071             {%base, ack_state => 'any', max_id => 30, count => 20},
1072             [reverse 11..30],
1073             'get: max_id in acked, any state, partial'
1074             );
1075 3         2523 get_and_check_list(
1076             $storage, $loop, $unloop,
1077             {%base, ack_state => 'any', max_id => 10, count => 40},
1078             [reverse 1..10],
1079             'get: max_id in acked, any state, count larger than the acked size'
1080             );
1081              
1082 3         1717 get_and_check_list(
1083             $storage, $loop, $unloop,
1084             {%base, ack_state => 'unacked', max_id => 45, count => 5},
1085             [reverse 41..45],
1086             'get: max_id in unacked, unacked state, count in unacked'
1087             );
1088 3         1710 get_and_check_list(
1089             $storage, $loop, $unloop,
1090             {%base, ack_state => 'unacked', max_id => 45, count => 25},
1091             [reverse 31..45],
1092             'get: max_id in unacked, unacked state, count larger than the unacked size'
1093             );
1094 3         2220 get_and_check_list(
1095             $storage, $loop, $unloop,
1096             {%base, ack_state => 'unacked', max_id => 20, count => 5},
1097             [],
1098             'get: max_id in acked, unacked state'
1099             );
1100              
1101 3         2391 get_and_check_list(
1102             $storage, $loop, $unloop,
1103             {%base, ack_state => 'acked', max_id => 50, count => 10},
1104             [],
1105             'get: max_id in unacked, acked state, count in unacked'
1106             );
1107 3         1678 get_and_check_list(
1108             $storage, $loop, $unloop,
1109             {%base, ack_state => 'acked', max_id => 45, count => 30},
1110             [],
1111             'get: max_id in unacked, acked state, count larger than the unacked size'
1112             );
1113 3         1830 get_and_check_list(
1114             $storage, $loop, $unloop,
1115             {%base, ack_state => 'acked', max_id => 20, count => 10},
1116             [reverse 11..20],
1117             'get: max_id in acked, acked state, count in acked'
1118             );
1119 3         1997 get_and_check_list(
1120             $storage, $loop, $unloop,
1121             {%base, ack_state => 'acked', max_id => 10, count => 30},
1122             [reverse 1..10],
1123             'get: max_id in acked, acked state, count larger than acked size'
1124             );
1125              
1126             {
1127 3         10709 note('--- more acked statuses');
  3         18  
1128 3         492 my $now = DateTime->now(time_zone => 'UTC');
1129 3         1051 my $yesterday = $now - DateTime::Duration->new(days => 1);
1130 3         2210 my $tomorrow = $now + DateTime::Duration->new(days => 1);
1131 30         124 my @more_statuses = (
1132 30         114 (map { status $_, 0, $datetime_formatter->format_datetime($tomorrow) } 61..70),
1133 3         1348 (map { status $_, 0, $datetime_formatter->format_datetime($yesterday) } 71..80)
1134             );
1135 3         41 change_and_check(
1136             $storage, $loop, $unloop, timeline => '_test_tl3',
1137             mode => 'insert', target => \@more_statuses,
1138             exp_change => 20, exp_unacked => [31..60], exp_acked => [1..30, 61..80]
1139             );
1140             }
1141             get_and_check_list(
1142 3         4711 $storage, $loop, $unloop,
1143             {%base, ack_state => 'any', count => 'all'},
1144             [reverse(71..80, 1..30, 61..70, 31..60)],
1145             'get: mixed acked_at, no max_id, any state, all'
1146             );
1147 3         4024 note('--- move from acked to unacked');
1148             on_statuses $storage, $loop, $unloop, {
1149             timeline => '_test_tl3', acked_state => 'acked',
1150             max_id => 30, count => 10
1151             }, sub {
1152 3     3   9 my $statuses = shift;
1153 3         52 delete $_->{busybird}{acked_at} foreach @$statuses;
1154 3         54 change_and_check(
1155             $storage, $loop, $unloop, timeline => '_test_tl3',
1156             mode => 'update', target => $statuses,
1157             exp_change => 10,
1158             exp_unacked => [21..60], exp_acked => [1..20, 61..80]
1159             );
1160 3         1367 };
1161 3         4326 get_and_check_list(
1162             $storage, $loop, $unloop,
1163             {%base, ack_state => 'any', count => 'all'},
1164             [reverse(71..80, 1..20, 61..70, 21..60)],
1165             'get:mixed acked_at, no max_id, any state, all'
1166             );
1167 3         4404 get_and_check_list(
1168             $storage, $loop, $unloop,
1169             {%base, ack_state => 'any', max_id => 30, count => 30},
1170             [reverse(11..20, 61..70, 21..30)],
1171             'get:mixed acked_at, max_id in unacked, any state, count larger than unacked size'
1172             );
1173 3         2759 get_and_check_list(
1174             $storage, $loop, $unloop,
1175             {%base, ack_state => 'any', max_id => 15, count => 20},
1176             [reverse(76..80, 1..15)],
1177             'get:mixed acked_at, max_id in acked, any state, count in acked'
1178             );
1179 3         3053 get_and_check_list(
1180             $storage, $loop, $unloop,
1181             {%base, ack_state => 'unacked', max_id => 50, count => 50},
1182             [reverse(21..50)],
1183             'get:mixed acked_at, max_id in unacked, unacked state, count larger than unacked size'
1184             );
1185 3         3190 get_and_check_list(
1186             $storage, $loop, $unloop,
1187             {%base, ack_state => 'acked', max_id => 65, count => 30},
1188             [reverse(76..80, 1..20, 61..65)],
1189             'get:mixed acked_at, max_id in acked, acked state, count in acked area'
1190             );
1191 3         3117 get_and_check_list(
1192             $storage, $loop, $unloop,
1193             {%base, ack_state => 'unacked', max_id => 20, count => 30},
1194             [],
1195             'get:mixed acked_at, max_id in acked, unacked state'
1196             );
1197 3         1859 get_and_check_list(
1198             $storage, $loop, $unloop,
1199             {%base, ack_state => 'acked', max_id => 40, count => 30},
1200             [],
1201             'get:mixed acked_at, max_id in unacked, acked state'
1202             );
1203              
1204 3         1874 note('--- messing with created_at');
1205             on_statuses $storage, $loop, $unloop, {
1206             timeline => '_test_tl3', count => 'all'
1207             }, sub {
1208 3     3   7 my $statuses = shift;
1209 3         17 is(int(@$statuses), 80, "80 statuses");
1210 3         1120 foreach my $s (@$statuses) {
1211 240         392213 $s->{created_at} = $datetime_formatter->format_datetime(
1212             $datetime_formatter->parse_datetime($s->{created_at})
1213             + DateTime::Duration->new(days => 100 - $s->{id})
1214             );
1215             }
1216             change_and_check(
1217 3         6388 $storage, $loop, $unloop, timeline => '_test_tl3',
1218             mode => 'update', target => $statuses, exp_change => 80,
1219             exp_unacked => [21..60], exp_acked => [1..20, 61..80]
1220             );
1221 3         605 };
1222 3         3809 get_and_check_list(
1223             $storage, $loop, $unloop,
1224             {%base, ack_state => 'any', count => 'all'},
1225             [21..60, 61..70, 1..20, 71..80],
1226             'sorted by descending order of created_at within acked_at group'
1227             );
1228              
1229 3         4059 note('--- -- ack test');
1230 3         431 note('--- change acked_at for testing');
1231             on_statuses $storage, $loop, $unloop, {
1232             %base, count => 'all', ack_state => 'acked'
1233             }, sub {
1234 3     3   7 my $statuses = shift;
1235 3         10 foreach my $s (@$statuses) {
1236 120         71747 $s->{busybird}{acked_at} =
1237             add_datetime_days($s->{busybird}{acked_at}, +2);
1238             }
1239             change_and_check(
1240 3         1989 $storage, $loop, $unloop, %base, mode => 'update',
1241             target => $statuses, exp_change => 40,
1242             exp_unacked => [21..60], exp_acked => [61..70, 1..20, 71..80]
1243             );
1244 3         358 };
1245 3         4104 change_and_check(
1246             $storage, $loop, $unloop, %base, mode => 'ack', target => 51,
1247             exp_change => 10, exp_unacked => [21..50], exp_acked => [61..70, 1..20, 71..80, 51..60]
1248             );
1249 3         4805 get_and_check_list(
1250             $storage, $loop, $unloop, {%base, ack_state => 'any', count => 'all'},
1251             [21..50, 61..70, 1..20, 71..80, 51..60],
1252             '10 acked statuses are at the bottom, because other acked statuses have acked_at of future.'
1253             );
1254            
1255 3         4208 note('--- populate another timeline');
1256 3         483 my %base4 = (timeline => '_test_tl4');
1257 3         9 $callbacked = 0;
1258             $storage->delete_statuses(%base4, ids => undef, callback => sub {
1259 3     3   10 my $error = shift;
1260 3         15 is($error, undef, "delete succeed");
1261 3         1211 $callbacked = 1;
1262 3         87 $unloop->();
1263 3         45 });
1264 3         22 $loop->();
1265 3         15 ok($callbacked, "callbacked");
1266 30         69 change_and_check(
1267             $storage, $loop, $unloop, %base4,
1268 3         1012 mode => 'insert', target => [map {status($_)} (31..40)],
1269             exp_change => 10, exp_unacked => [31..40], exp_acked => []
1270             );
1271 3         2446 get_and_check_list(
1272             $storage, $loop, $unloop, {%base4, count => 'all'}, [reverse 31..40],
1273             '10 unacked'
1274             );
1275 3         2081 change_and_check(
1276             $storage, $loop, $unloop, %base4,
1277             mode => 'ack', target => 35, exp_change => 5,
1278             exp_unacked => [36..40], exp_acked => [31..35]
1279             );
1280 3         2848 get_and_check_list(
1281             $storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'acked'},
1282             [reverse 31..35], '5 acked'
1283             );
1284 30         78 change_and_check(
1285             $storage, $loop, $unloop, %base4,
1286 3         2576 mode => 'insert', target => [map {status($_)} (26..30, 41..45)],
1287             exp_change => 10, exp_unacked => [26..30, 36..45], exp_acked => [31..35]
1288             );
1289 3         2708 get_and_check_list(
1290             $storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'unacked'},
1291             [reverse 26..30, 36..45], '15 unacked statuses'
1292             );
1293 3         2447 note('--- For testing, set acked_at sufficiently old.');
1294             on_statuses $storage, $loop, $unloop, {
1295             %base4, count => 'all', ack_state => 'acked'
1296             }, sub {
1297 3     3   9 my $statuses = shift;
1298 3         12 foreach my $s (@$statuses) {
1299 15         10614 $s->{busybird}{acked_at} = add_datetime_days($s->{busybird}{acked_at}, -1);
1300             }
1301             change_and_check(
1302 3         2099 $storage, $loop, $unloop, %base4, mode => 'update', target => $statuses,
1303             exp_change => 5, exp_unacked => [26..30, 36..45], exp_acked => [31..35]
1304             );
1305 3         517 };
1306 3         2432 change_and_check(
1307             $storage, $loop, $unloop, %base4, mode => 'ack', target => 40, exp_change => 10,
1308             exp_unacked => [41..45], exp_acked => [36..40, 26..30, 31..35]
1309             );
1310 3         2243 get_and_check_list(
1311             $storage, $loop, $unloop, {%base4, count => 'all', ack_state => 'acked'},
1312             [reverse(36..40), reverse(26..30), reverse(31..35)]
1313             );
1314 3         2033 change_and_check(
1315             $storage, $loop, $unloop, %base4, mode => 'ack', exp_change => 5,
1316             exp_unacked => [], exp_acked => [26..45]
1317             );
1318             {
1319 3         2397 note('--- same timestamp: order is free, but must be consistent.');
  3         17  
1320 3         549 my %base5 = (timeline => '_test_tl5');
1321 3         13 my @in_statuses = map {status($_)} (1..10);
  30         70  
1322 3         16 my $created_at = nowstring;
1323 3         1321 $_->{created_at} = $created_at foreach @in_statuses;
1324 3         39 change_and_check(
1325             $storage, $loop, $unloop, %base5, mode => 'insert', target => [@in_statuses[0..4]],
1326             label => 'insert first five', exp_change => 5, exp_unacked => [1..5], exp_acked => []
1327             );
1328 3         2162 change_and_check(
1329             $storage, $loop, $unloop, %base5, mode => 'ack', target => undef,
1330             label => 'ack first five', exp_change => 5, exp_unacked => [], exp_acked => [1..5]
1331             );
1332 3         2443 change_and_check(
1333             $storage, $loop, $unloop, %base5, mode => 'insert', target => [@in_statuses[5..9]],
1334             label => 'insert next five', exp_change => 5, exp_unacked => [6..10], exp_acked => [1..5]
1335             );
1336 3         2603 my $whole_timeline = sync_get($storage, $loop, $unloop, %base5, count => 'all');
1337 3         14 foreach my $start_index (0..9) {
1338 30         19802 my $max_id = $whole_timeline->[$start_index]{id};
1339 165         459 get_and_check_list(
1340             $storage, $loop, $unloop, {%base5, count => 'all', max_id => $max_id},
1341 30         263 [ map {$_->{id}} @{$whole_timeline}[$start_index .. 9] ],
  30         137  
1342             "start_index = $start_index, max_id = $max_id: order is the same as the whole_timeline"
1343             );
1344             }
1345             }
1346              
1347             {
1348 3         1974 note('--- -- acks with various argument cases (ordered)');
  3         16  
1349 3         770 foreach my $case (test_cases_for_ack(is_ordered => 1)) {
1350 15         8040 my $callbacked = 0;
1351 15 50       80 next if not defined $case->{req};
1352 15         120 note("--- case: $case->{label}");
1353             $storage->delete_statuses(timeline => '_test_acks', ids => undef, callback => sub {
1354 15     15   35 my ($error, $count) = @_;
1355 15         94 is($error, undef, "delete succeed");
1356 15         8171 $callbacked = 1;
1357 15         67 $unloop->();
1358 15         3282 });
1359 15         111 $loop->();
1360 15         71 ok($callbacked, 'callbacked');
1361 15         6839 my $already_acked_at = add_datetime_days(nowstring(), -1);
1362 150         479 change_and_check(
1363             $storage, $loop, $unloop, timeline => '_test_acks', mode => 'insert',
1364 15         13434 target => [(map {status($_, 0, $already_acked_at)} 1..10), (map {status($_)} 11..20)],
  150         294  
1365             exp_change => 20, exp_acked => [1..10], exp_unacked => [11..20]
1366             );
1367 15         12607 my %target_args = ();
1368 15         257 $storage->ack_statuses(timeline => '_test_acks', %{$case->{req}}, callback => sub {
1369 15     15   53 my ($error, $count) = @_;
1370 15         114 is($error, undef, "ack succeed");
1371 15         10238 is($count, $case->{exp_count}, "count is $case->{exp_count}");
1372 15         6167 $callbacked = 1;
1373 15         78 $unloop->();
1374 15         39 });
1375 15         1395 $loop->();
1376 15         79 ok($callbacked, "callbacked");
1377 15         6009 get_and_check_list(
1378             $storage, $loop, $unloop, {timeline => '_test_acks', count => 'all', ack_state => 'acked'},
1379             $case->{exp_acked}, "ordered acked statuses OK"
1380             );
1381 15         11974 get_and_check_list(
1382             $storage, $loop, $unloop, {timeline => '_test_acks', count => 'all', ack_state => 'unacked'},
1383             $case->{exp_unacked}, "ordered unacked statuses OK"
1384             );
1385             }
1386             }
1387             }
1388              
1389             sub test_storage_truncation {
1390 3     3 1 451 my ($storage, $options, $loop, $unloop) = @_;
1391 3         91 note("-------- test_storage_truncation");
1392 3 50 33     850 if(!defined($options) || ref($options) ne 'HASH') {
1393 0         0 croak "options must be a hash-ref";
1394             }
1395 3 50       16 croak "soft_max option is mandatory" if not defined $options->{soft_max};
1396 3         12 my $soft_max = int($options->{soft_max});
1397 3 50       11 croak "soft_max must be bigger than 0" if !($soft_max > 0);
1398 3 50       12 my $hard_max = defined($options->{hard_max}) ? $options->{hard_max} : $soft_max;
1399 3         7 $hard_max = int($hard_max);
1400 3 50       8 croak "hard_max must be >= soft_max" if !($hard_max >= $soft_max);
1401 3         21 note("--- soft_max = $soft_max, hard_max = $hard_max");
1402 3   50 123   525 $loop ||= sub {};
  123         255  
1403 3   50 123   24 $unloop ||= sub {};
  123         395  
1404            
1405 3         12 note('--- clear the timeline');
1406 3         591 my $callbacked = 0;
1407 3         16 my %base = (timeline => '_test_tl4');
1408             $storage->delete_statuses(%base, ids => undef, callback => sub {
1409 3     3   9 my $error = shift;
1410 3         15 is($error, undef, "delete succeed");
1411 3         1721 $callbacked = 1;
1412 3         15 $unloop->();
1413 3         40 });
1414 3         21 $loop->();
1415 3         17 ok($callbacked, 'callbacked');
1416             on_statuses $storage, $loop, $unloop, {
1417             %base, count => 'all'
1418             }, sub {
1419 3     3   9 my ($statuses) = @_;
1420 3         17 is(int(@$statuses), 0, 'no statuses');
1421 3         1511 };
1422 3         1259 note('--- populate to the max');
1423 25         60 change_and_check(
1424             $storage, $loop, $unloop, %base,
1425 3         657 mode => 'insert', target => [map {status($_)} (1..$hard_max)],
1426             exp_change => $hard_max, exp_unacked => [1..$hard_max],
1427             exp_acked => []
1428             );
1429 3         2182 note('--- insert another one: truncation occurs');
1430 3         650 change_and_check(
1431             $storage, $loop, $unloop, %base,
1432             mode => 'insert', target => status($hard_max+1),
1433             exp_change => 1, exp_unacked => [($hard_max+1 - ($soft_max-1))..($hard_max+1)],
1434             exp_acked => []
1435             );
1436 3         2073 note('--- insert multiple statuses: truncation occurs');
1437 40         118 change_and_check(
1438             $storage, $loop, $unloop, %base,
1439 3         531 mode => 'insert', target => [map { status($_) } ($hard_max+2) .. ($hard_max*2 - $soft_max + 11)],
1440             exp_change => ($hard_max - $soft_max + 10),
1441             exp_unacked => [($hard_max*2 - $soft_max*2 + 12) .. ($hard_max*2 - $soft_max + 11)],
1442             exp_acked => []
1443             );
1444              
1445 3         2711 note('--- clear and populate to the max');
1446 3         638 change_and_check(
1447             $storage, $loop, $unloop, %base,
1448             mode => 'delete', target => undef,
1449             exp_change => $soft_max, exp_unacked => [], exp_acked => []
1450             );
1451 25         51 change_and_check(
1452             $storage, $loop, $unloop, %base,
1453 3         2031 mode => 'insert', target => [map {status($_)} 1..$hard_max],
1454             exp_change => $hard_max, exp_unacked => [1..$hard_max], exp_acked => []
1455             );
1456 3         2578 note('--- ack the top status');
1457             on_statuses $storage, $loop, $unloop, {
1458             %base, count => 1, max_id => $hard_max
1459             }, sub {
1460 3     3   10 my ($statuses) = @_;
1461 3         16 $statuses->[0]{busybird}{acked_at} = nowstring();
1462 3         2750 change_and_check(
1463             $storage, $loop, $unloop, %base,
1464             mode => 'update', target => $statuses,
1465             exp_change => 1, exp_unacked => [1..($hard_max-1)],
1466             exp_acked => [$hard_max]
1467             );
1468 3         636 };
1469 3         2634 note('--- inserting another one removes the acked status');
1470 3         655 change_and_check(
1471             $storage, $loop, $unloop, %base,
1472             mode => 'insert', target => status($hard_max+1),
1473             exp_change => 1, exp_unacked => [($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)],
1474             exp_acked => []
1475             );
1476 3         2066 note('--- populate timeline to the max');
1477 10         26 change_and_check(
1478             $storage, $loop, $unloop, %base,
1479 3         551 mode => 'insert', target => [map {status($_)} ($hard_max+2) .. ($hard_max+2 + $hard_max - $soft_max - 1)],
1480             exp_change => $hard_max - $soft_max,
1481             exp_unacked => [($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)..($hard_max*2+2 - $soft_max-1)],
1482             exp_acked => []
1483             );
1484 3         2028 note('--- clear another timeline');
1485             $storage->delete_statuses(timeline => '_test_tl4_2', ids => undef, callback => sub {
1486 3     3   10 my $error = shift;
1487 3         13 is($error, undef, "delete succeed");
1488 3         1360 $callbacked = 1;
1489 3         12 $unloop->();
1490 3         570 });
1491 3         15 $loop->();
1492 3         13 ok($callbacked, "callbacked");
1493 3         1104 note('--- populate another timeline to the max');
1494 25         55 change_and_check(
1495             $storage, $loop, $unloop, timeline => '_test_tl4_2',
1496 3         473 mode => 'insert', target => [map {status($_)} 1..$hard_max],
1497             exp_change => $hard_max, exp_unacked => [1..$hard_max], exp_acked => []
1498             );
1499 3         3463 note('--- statuses in the first timeline is maintained.');
1500             on_statuses $storage, $loop, $unloop, {
1501             %base, count => 'all'
1502             }, sub {
1503 3     3   9 my $statuses = shift;
1504 3         64 test_status_id_list(
1505             $statuses, [reverse( ($hard_max - $soft_max + 1)..($hard_max - 1), ($hard_max + 1)..($hard_max*2+2 - $soft_max-1) )],
1506             "statuses in the first timeline are intact"
1507             );
1508 3         682 };
1509             }
1510              
1511             sub test_storage_missing_arguments {
1512 3     3 1 2185 my ($storage, $loop, $unloop) = @_;
1513 3         16 note("-------- test_storage_missing_arguments");
1514 3     3   583 dies_ok { $storage->ack_statuses() } 'ack: timeline is missing';
  3         246  
1515 3     3   1419 dies_ok { $storage->get_statuses(callback => sub {}) } 'get: timeline is missing';
  3         146  
  0         0  
1516 3     3   1525 dies_ok { $storage->get_statuses(timeline => 'tl') } 'get: callback is missing';
  3         147  
1517             dies_ok {
1518 3     3   177 $storage->put_statuses(mode => 'insert', statuses => []);
1519 3         1315 } 'put: timeline is missing';
1520             dies_ok {
1521 3     3   159 $storage->put_statuses(timeline => 'tl', mode => 'insert');
1522 3         1448 } 'put: statuses is missing';
1523             dies_ok {
1524 3     3   139 $storage->put_statuses(timeline => 'tl', statuses => []);
1525 3         1348 } 'put: mode is missing';
1526 3     3   1583 dies_ok { $storage->delete_statuses(ids => undef) } 'delete: timeline is missing';
  3         145  
1527 3     3   1348 dies_ok { $storage->delete_statuses(timeline => 'tl') } 'delete: ids is missing';
  3         160  
1528 3     3   1334 dies_ok { $storage->get_unacked_counts(callback => sub {}) } 'get_unacked: timeline is missing';
  3         144  
  0         0  
1529 3     3   1418 dies_ok { $storage->get_unacked_counts(timeline => 'tl') } 'get_unacked: callback is missing';
  3         144  
1530 3     3   1314 dies_ok { $storage->contains(query => 10, callback => sub {}) } 'contains: timeline is missing';
  3         146  
  0         0  
1531 3     3   1458 dies_ok { $storage->contains(timeline => 'tl', callback => sub {}) } 'contains: query is missing';
  3         162  
  0         0  
1532 3     3   1352 dies_ok { $storage->contains(timeline => 'tl', query => 10) } 'contains: callback is missing';
  3         154  
1533             }
1534              
1535             sub test_storage_requires_status_ids {
1536 3     3 1 1520 my ($storage, $loop, $unloop) = @_;
1537 3         19 note("-------- test_storage_requires_status_ids");
1538 3   50 9   602 $loop ||= sub {};
  9         17  
1539 3   50 9   26 $unloop ||= sub {};
  9         27  
1540 3         16 my %cases = (
1541             no_id => status(1),
1542             undef_id => status(2),
1543             );
1544 3         11 my $ok_status = status(3);
1545 3         13 delete $cases{no_id}{id};
1546 3         13 $cases{undef_id}{id} = undef;
1547 3     0   24 my %base = (timeline => '_test_tl_requires_status_ids', callback => sub { fail("callbacked") });
  0         0  
1548 3         6 my $callbacked = 0;
1549             $storage->delete_statuses(%base, ids => undef, callback => sub {
1550 3     3   9 my $error = shift;
1551 3         19 is($error, undef, 'delete succeed');
1552 3         2023 $callbacked = 1;
1553 3         14 $unloop->();
1554 3         36 });
1555 3         20 $loop->();
1556 3         14 ok($callbacked, 'callbacked');
1557 3         1135 foreach my $case (keys %cases) {
1558 6         1244 my $s = $cases{$case};
1559 6     6   76 dies_ok { $storage->put_statuses(%base, mode => 'insert', statuses => $s) } "case: $case, insert, single: dies OK";
  6         429  
1560 6     6   3322 dies_ok { $storage->put_statuses(%base, mode => 'update', statuses => $s) } "case: $case, update, single: dies OK";
  6         269  
1561 6     6   3383 dies_ok { $storage->put_statuses(%base, mode => 'upsert', statuses => $s) } "case: $case, upsert, single: dies OK";
  6         308  
1562 6     6   3108 dies_ok { $storage->put_statuses(%base, mode => 'insert', statuses => [$ok_status, $s]) } "case: $case, insert, array: dies OK";
  6         269  
1563 6     6   3144 dies_ok { $storage->put_statuses(%base, mode => 'update', statuses => [$ok_status, $s]) } "case: $case, update, array: dies OK";
  6         275  
1564 6     6   3517 dies_ok { $storage->put_statuses(%base, mode => 'upsert', statuses => [$ok_status, $s]) } "case: $case, upsert, array: dies OK";
  6         288  
1565 6         3295 my $statuses = sync_get($storage, $loop, $unloop, timeline => $base{timeline}, count => 'all');
1566 6         31 is(int(@$statuses), 0, 'storage is empty');
1567             }
1568             }
1569              
1570             sub test_storage_undef_in_array {
1571 3     3 1 1221 my ($storage, $loop, $unloop) = @_;
1572 3         16 note("-------- test_storage_undef_in_array");
1573 3     0   562 my %base = (timeline => '_timeline_undef_in_array', callback => sub { fail("callbacked") });
  0         0  
1574 3     3   22 dies_ok { $storage->ack_statuses(%base, ids => [1, 10, undef]) } "ack dies OK";
  3         157  
1575 3         1527 foreach my $mode (qw(insert update upsert)) {
1576 9     9   2980 dies_ok { $storage->put_statuses(%base, mode => $mode, statuses => [undef, {id => 10}]) } "$mode dies OK";
  9         368  
1577             }
1578 3     3   1420 dies_ok { $storage->delete_statuses(%base, ids => [undef, undef, 9]) } "delete dies OK";
  3         131  
1579 3     3   1321 dies_ok { $storage->contains(%base, query => [undef, 10, {id => 8}]) } "contains dies OK";
  3         153  
1580             }
1581              
1582              
1583             1;
1584              
1585             __END__