File Coverage

blib/lib/BusyBird/Test/StatusStorage.pm
Criterion Covered Total %
statement 823 831 99.0
branch 49 58 84.4
condition 18 29 62.0
subroutine 127 129 98.4
pod 8 21 38.1
total 1025 1068 95.9


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