lei/store: stop shard workers + cat-file on idle
[public-inbox.git] / t / lei-import.t
blob89eb149261162dde66c3574366d58590a9f26cee
1 #!perl -w
2 # Copyright (C) all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 use v5.12; use PublicInbox::TestCommon;
5 use PublicInbox::DS qw(now);
6 use PublicInbox::IO qw(write_file);
7 use autodie qw(open close truncate);
8 test_lei(sub {
9 ok(!lei(qw(import -F bogus), 't/plack-qp.eml'), 'fails with bogus format');
10 like($lei_err, qr/\bis `eml', not --in-format/, 'gave error message');
12 lei_ok(qw(q s:boolean), \'search miss before import');
13 unlike($lei_out, qr/boolean/i, 'no results, yet');
14 open my $fh, '<', 't/data/0001.patch';
15 lei_ok([qw(import -F eml -)], undef, { %$lei_opt, 0 => $fh },
16         \'import single file from stdin') or diag $lei_err;
17 close $fh;
18 lei_ok(qw(q s:boolean), \'search hit after import');
19 lei_ok(qw(q s:boolean -f mboxrd), \'blob accessible after import');
21         my $expect = [ eml_load('t/data/0001.patch') ];
22         require PublicInbox::MboxReader;
23         my @cmp;
24         open my $fh, '<', \$lei_out;
25         PublicInbox::MboxReader->mboxrd($fh, sub {
26                 my ($eml) = @_;
27                 $eml->header_set('Status');
28                 push @cmp, $eml;
29         });
30         is_deeply(\@cmp, $expect, 'got expected message in mboxrd');
32 lei_ok(qw(import -F eml), 't/data/message_embed.eml',
33         \'import single file by path');
35 lei_ok(qw(q m:testmessage@example.com));
36 is($lei_out, "[null]\n", 'no results, yet');
37 my $oid = '9bf1002c49eb075df47247b74d69bcd555e23422';
38 my $eml = eml_load('t/utf8.eml');
39 my $in = 'From x@y Fri Oct  2 00:00:00 1993'."\n".$eml->as_string;
40 lei_ok([qw(import -F eml -)], undef, { %$lei_opt, 0 => \$in });
41 lei_ok(qw(q m:testmessage@example.com));
42 is(json_utf8->decode($lei_out)->[0]->{'blob'}, $oid,
43         'got expected OID w/o From');
45 my $eml_str = <<'';
46 From: a@b
47 Message-ID: <x@y>
48 Status: RO
50 my $opt = { %$lei_opt, 0 => \$eml_str };
51 lei_ok([qw(import -F eml -)], undef, $opt,
52         \'import single file with keywords from stdin');
53 lei_ok(qw(q m:x@y));
54 my $res = json_utf8->decode($lei_out);
55 is($res->[1], undef, 'only one result');
56 is($res->[0]->{'m'}, 'x@y', 'got expected message');
57 is($res->[0]->{kw}, undef, 'Status ignored for eml');
58 lei_ok(qw(q -f mboxrd m:x@y));
59 unlike($lei_out, qr/^Status:/, 'no Status: in imported message');
60 lei_ok('blob', $res->[0]->{blob});
61 is($lei_out, "From: a\@b\nMessage-ID: <x\@y>\n", 'got blob back');
64 $eml->header_set('Message-ID', '<v@y>');
65 $eml->header_set('Status', 'RO');
66 $in = 'From v@y Fri Oct  2 00:00:00 1993'."\n".$eml->as_string;
67 lei_ok([qw(import --no-kw -F mboxrd -)], undef, { %$lei_opt, 0 => \$in },
68         \'import single file with --no-kw from stdin');
69 lei(qw(q m:v@y));
70 $res = json_utf8->decode($lei_out);
71 is($res->[1], undef, 'only one result');
72 is($res->[0]->{'m'}, 'v@y', 'got expected message');
73 is($res->[0]->{kw}, undef, 'no keywords set');
75 $eml->header_set('Message-ID', '<k@y>');
76 $in = 'From k@y Fri Oct  2 00:00:00 1993'."\n".$eml->as_string;
77 lei_ok([qw(import -F mboxrd /dev/fd/0)], undef, { %$lei_opt, 0 => \$in },
78         \'import single file with --kw (default) from stdin');
79 lei(qw(q m:k@y));
80 $res = json_utf8->decode($lei_out);
81 is($res->[1], undef, 'only one result');
82 is($res->[0]->{'m'}, 'k@y', 'got expected message');
83 is_deeply($res->[0]->{kw}, ['seen'], "`seen' keywords set");
85 # no From, Sender, or Message-ID
86 $eml_str = <<'EOM';
87 Subject: draft message with no sender
88 References: <y@y>
89 Resent-Message-ID: <resent-test@example>
91 No use for a name
92 EOM
93 lei_ok([qw(import -F eml -)], undef, { %$lei_opt, 0 => \$eml_str });
94 lei_ok(['q', 's:draft message with no sender']);
95 my $draft_a = json_utf8->decode($lei_out);
96 ok(!exists $draft_a->[0]->{'m'}, 'no fake mid stored or exposed');
97 lei_ok([qw(tag -F eml - +kw:draft)], undef, { %$lei_opt, 0 => \$eml_str });
98 lei_ok(['q', 's:draft message with no sender']);
99 my $draft_b = json_utf8->decode($lei_out);
100 my $kw = delete $draft_b->[0]->{kw};
101 is_deeply($kw, ['draft'], 'draft kw set');
102 is_deeply($draft_a, $draft_b, 'fake Message-ID lookup') or
103                                 diag explain($draft_a, $draft_b);
104 lei_ok('blob', '--mail', $draft_b->[0]->{blob});
105 is($lei_out, $eml_str, 'draft retrieved by blob');
108 $eml_str = "Message-ID: <inbox\@example.com>\nSubject: label-this\n\n";
109 lei_ok([qw(import -F eml - +kw:seen +L:inbox)],
110         undef, { %$lei_opt, 0 => \$eml_str });
111 lei_ok(qw(q m:inbox@example.com));
112 $res = json_utf8->decode($lei_out);
113 is_deeply($res->[0]->{kw}, ['seen'], 'keyword set');
114 is_deeply($res->[0]->{L}, ['inbox'], 'label set');
116 # idempotent import can add label
117 lei_ok([qw(import -F eml - +L:boombox)],
118         undef, { %$lei_opt, 0 => \$eml_str });
119 lei_ok(qw(q m:inbox@example.com));
120 $res = json_utf8->decode($lei_out);
121 is_deeply($res->[0]->{kw}, ['seen'], 'keyword remains set');
122 is_deeply($res->[0]->{L}, [qw(boombox inbox)], 'new label added');
124 # idempotent import can add keyword
125 lei_ok([qw(import -F eml - +kw:answered)],
126         undef, { %$lei_opt, 0 => \$eml_str });
127 lei_ok(qw(q m:inbox@example.com));
128 $res = json_utf8->decode($lei_out);
129 is_deeply($res->[0]->{kw}, [qw(answered seen)], 'keyword added');
130 is_deeply($res->[0]->{L}, [qw(boombox inbox)], 'labels preserved');
132 # +kw:seen is not a location
133 open my $null, '<', '/dev/null';
134 ok(!lei([qw(import -F eml +kw:seen)], undef, { %$lei_opt, 0 => $null }),
135         'import fails w/ only kw arg');
136 like($lei_err, qr/\bLOCATION\.\.\. or --stdin must be set/s, 'error message');
138 lei_ok([qw(import -F eml +kw:flagged)], # no lone dash (`-')
139         undef, { %$lei_opt, 0 => \$eml_str },
140         'import succeeds with implicit --stdin');
141 lei_ok(qw(q m:inbox@example.com));
142 $res = json_utf8->decode($lei_out);
143 is_deeply($res->[0]->{kw}, [qw(answered flagged seen)], 'keyword added');
144 is_deeply($res->[0]->{L}, [qw(boombox inbox)], 'labels preserved');
146 lei_ok qw(import --commit-delay=1 +L:bin -F eml t/data/binary.patch);
147 lei_ok 'ls-label';
148 unlike($lei_out, qr/\bbin\b/, 'commit-delay delays label');
149 my $end = now + 10;
150 my $n = 1;
151 diag 'waiting for lei/store commit...';
152 do {
153         tick $n;
154         $n = 0.1;
155 } until (!lei('ls-label') || $lei_out =~ /\bbin\b/ || now > $end);
156 like($lei_out, qr/\bbin\b/, 'commit-delay eventually commits');
158 SKIP: {
159         my $strace = strace_inject(1); # skips if strace is old or non-Linux
160         my $tmpdir = tmpdir;
161         my $tr = "$tmpdir/tr";
162         my $cmd = [ $strace, '-q', "-o$tr", '-f',
163                 "-P", File::Spec->rel2abs('t/plack-qp.eml'),
164                 '-e', 'inject=readv,read:error=EIO'];
165         lei_ok qw(daemon-pid);
166         chomp(my $daemon_pid = $lei_out);
167         push @$cmd, '-p', $daemon_pid;
168         require PublicInbox::Spawn;
169         require PublicInbox::AutoReap;
170         my $pid = PublicInbox::Spawn::spawn($cmd, \%ENV);
171         my $ar = PublicInbox::AutoReap->new($pid);
172         tick; # wait for strace to attach
173         ok(!lei(qw(import -F eml t/plack-qp.eml)),
174                 '-F eml import fails on pathname error injection');
175         my $IO = '[Ii](?:nput)?/[Oo](?:utput)?';
176         like($lei_err, qr!error reading t/plack-qp\.eml: .*?$IO error!,
177                 'EIO noted in stderr');
178         open $fh, '<', 't/plack-qp.eml';
179         ok(!lei(qw(import -F eml -), undef, { %$lei_opt, 0 => $fh }),
180                 '-F eml import fails on stdin error injection');
181         like($lei_err, qr!error reading .*?: .*?$IO error!,
182                 'EIO noted in stderr');
186         local $ENV{PI_CONFIG} = "$ENV{HOME}/pi_config";
187         write_file '>', $ENV{PI_CONFIG}, <<EOM;
188 [publicinboxImport]
189         dropUniqueUnsubscribe
191         my $in = <<EOM;
192 List-Unsubscribe: <https://example.com/some-UUID-here/test>
193 List-Unsubscribe-Post: List-Unsubscribe=One-Click
194 Message-ID: <unsubscribe-1\@example>
195 Subject: unsubscribe-1 example
196 From: u\@example.com
197 To: 2\@example.com
198 Date: Fri, 02 Oct 1993 00:00:00 +0000
201         lei_ok [qw(import -F eml +L:unsub)], undef, { %$lei_opt, 0 => \$in },
202                 'import succeeds w/ List-Unsubscribe';
203         lei_ok qw(q L:unsub -f mboxrd);
204         like $lei_out, qr/some-UUID-here/,
205                 'Unsubscribe header preserved despite PI_CONFIG dropping';
206         lei_ok qw(q L:unsub -o), "v2:$ENV{HOME}/v2-1";
207         lei_ok qw(q s:unsubscribe -f mboxrd --only), "$ENV{HOME}/v2-1";
208         unlike $lei_out, qr/some-UUID-here/,
209                 'Unsubscribe header dropped w/ dropUniqueUnsubscribe';
210         like $lei_out, qr/Message-ID: <unsubscribe-1\@example>/,
211                 'wrote expected message to v2 output';
213         # the default for compatibility:
214         truncate $ENV{PI_CONFIG}, 0;
215         lei_ok qw(q L:unsub -o), "v2:$ENV{HOME}/v2-2";
216         lei_ok qw(q s:unsubscribe -f mboxrd --only), "$ENV{HOME}/v2-2";
217         like $lei_out, qr/some-UUID-here/,
218                 'Unsubscribe header preserved by default :<';
220         # ensure we can fail
221         write_file '>', $ENV{PI_CONFIG}, <<EOM;
222 [publicinboxImport]
223         dropUniqueUnsubscribe = bogus
225         ok(!lei(qw(q L:unsub -o), "v2:$ENV{HOME}/v2-3"), 'bad config fails');
226         like $lei_err, qr/is not boolean/, 'non-booleaness noted in stderr';
227         ok !-d "$ENV{HOME}/v2-3", 'v2 directory not created';
230 # see t/lei_to_mail.t for "import -F mbox*"
232 done_testing;