]> Sergey Matveev's repositories - public-inbox.git/blob - t/lei-q-kw.t
lei <q|up>: set \Recent on non-empty mbox and Maildir
[public-inbox.git] / t / lei-q-kw.t
1 #!perl -w
2 # Copyright (C) 2020-2021 all contributors <meta@public-inbox.org>
3 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
4 use strict; use v5.10.1; use PublicInbox::TestCommon;
5 use POSIX qw(mkfifo);
6 use Fcntl qw(SEEK_SET O_RDONLY O_NONBLOCK);
7 use IO::Uncompress::Gunzip qw(gunzip);
8 use IO::Compress::Gzip qw(gzip);
9 use PublicInbox::MboxReader;
10 use PublicInbox::LeiToMail;
11 use PublicInbox::Spawn qw(popen_rd);
12 my $exp = {
13         '<qp@example.com>' => eml_load('t/plack-qp.eml'),
14         '<testmessage@example.com>' => eml_load('t/utf8.eml'),
15 };
16 $exp->{'<qp@example.com>'}->header_set('Status', 'RO');
17
18 test_lei(sub {
19 lei_ok(qw(import -F eml t/plack-qp.eml));
20 my $o = "$ENV{HOME}/dst";
21 lei_ok(qw(q -o), "maildir:$o", qw(m:qp@example.com));
22 my @fn = glob("$o/cur/*:2,");
23 scalar(@fn) == 1 or xbail $lei_err, 'wrote multiple or zero files:', \@fn;
24 rename($fn[0], "$fn[0]S") or BAIL_OUT "rename $!";
25
26 lei_ok(qw(q -o), "maildir:$o", qw(m:bogus-noresults@example.com));
27 ok(!glob("$o/cur/*"), 'last result cleared after augment-import');
28
29 lei_ok(qw(q -o), "maildir:$o", qw(m:qp@example.com));
30 @fn = glob("$o/cur/*:2,S");
31 is(scalar(@fn), 1, "`seen' flag set on Maildir file");
32
33 # ensure --no-import-before works
34 my $n = $fn[0];
35 $n =~ s/,S\z/,RS/;
36 rename($fn[0], $n) or BAIL_OUT "rename $!";
37 lei_ok(qw(q --no-import-before -o), "maildir:$o",
38         qw(m:bogus-noresults@example.com));
39 ok(!glob("$o/cur/*"), '--no-import-before cleared destination');
40 lei_ok(qw(q -o), "maildir:$o", qw(m:qp@example.com));
41 @fn = glob("$o/cur/*:2,S");
42 is(scalar(@fn), 1, "`seen' flag (but not `replied') set on Maildir file");
43
44 SKIP: {
45         $o = "$ENV{HOME}/fifo";
46         mkfifo($o, 0600) or skip("mkfifo not supported: $!", 1);
47         # cat(1) since lei() may not execve for FD_CLOEXEC to work
48         my $cat = popen_rd(['cat', $o]);
49         ok(!lei(qw(q --import-before bogus -o), "mboxrd:$o"),
50                 '--import-before fails on non-seekable output');
51         is(do { local $/; <$cat> }, '', 'no output on FIFO');
52         close $cat;
53         $cat = popen_rd(['cat', $o]);
54         lei_ok(qw(q m:qp@example.com -o), "mboxrd:$o");
55         my $buf = do { local $/; <$cat> };
56         open my $fh, '<', \$buf or BAIL_OUT $!;
57         PublicInbox::MboxReader->mboxrd($fh, sub {
58                 my ($eml) = @_;
59                 $eml->header_set('Status', 'RO');
60                 is_deeply($eml, $exp->{'<qp@example.com>'},
61                         'FIFO output works as expected');
62         });
63 };
64
65 lei_ok qw(import -F eml t/utf8.eml), \'for augment test';
66 my $read_file = sub {
67         if ($_[0] =~ /\.gz\z/) {
68                 gunzip($_[0] => \(my $buf = ''), MultiStream => 1) or
69                         BAIL_OUT 'gunzip';
70                 $buf;
71         } else {
72                 open my $fh, '+<', $_[0] or BAIL_OUT $!;
73                 do { local $/; <$fh> };
74         }
75 };
76
77 my $write_file = sub {
78         if ($_[0] =~ /\.gz\z/) {
79                 gzip(\($_[1]), $_[0]) or BAIL_OUT 'gzip';
80         } else {
81                 open my $fh, '>', $_[0] or BAIL_OUT $!;
82                 print $fh $_[1] or BAIL_OUT $!;
83                 close $fh or BAIL_OUT;
84         }
85 };
86
87 for my $sfx ('', '.gz') {
88         $o = "$ENV{HOME}/dst.mboxrd$sfx";
89         lei_ok(qw(q -o), "mboxrd:$o", qw(m:qp@example.com));
90         my $buf = $read_file->($o);
91         $buf =~ s/^Status: [^\n]*\n//sm or BAIL_OUT "no status in $buf";
92         $write_file->($o, $buf);
93         lei_ok(qw(q -o), "mboxrd:$o", qw(rereadandimportkwchange));
94         $buf = $read_file->($o);
95         is($buf, '', 'emptied');
96         lei_ok(qw(q -o), "mboxrd:$o", qw(m:qp@example.com));
97         $buf = $read_file->($o);
98         $buf =~ s/\nStatus: O\n\n/\nStatus: RO\n\n/s or
99                 BAIL_OUT "no Status in $buf";
100         $write_file->($o, $buf);
101         lei_ok(qw(q -a -o), "mboxrd:$o", qw(m:testmessage@example.com));
102         $buf = $read_file->($o);
103         open my $fh, '<', \$buf or BAIL_OUT "PerlIO::scalar $!";
104         my %res;
105         PublicInbox::MboxReader->mboxrd($fh, sub {
106                 my ($eml) = @_;
107                 my $mid = $eml->header_raw('Message-ID');
108                 if ($mid eq '<testmessage@example.com>') {
109                         is_deeply([$eml->header('Status')], [],
110                                 "no status $sfx");
111                         $eml->header_set('Status');
112                 } elsif ($mid eq '<qp@example.com>') {
113                         is($eml->header('Status'), 'RO', 'status preserved');
114                 } else {
115                         fail("unknown mid $mid");
116                 }
117                 $res{$mid} = $eml;
118         });
119         is_deeply(\%res, $exp, '--augment worked');
120
121         lei_ok(qw(q -o), "mboxrd:/dev/stdout", qw(m:qp@example.com)) or
122                 diag $lei_err;
123         like($lei_out, qr/^Status: RO\n/sm, 'Status set by previous augment');
124 } # /mbox + mbox.gz tests
125
126 my ($ro_home, $cfg_path) = setup_public_inboxes;
127
128 # import keywords-only for external messages:
129 $o = "$ENV{HOME}/kwdir";
130 my $m = 'alpine.DEB.2.20.1608131214070.4924@example';
131 my @inc = ('-I', "$ro_home/t1");
132 lei_ok(qw(q -o), $o, "m:$m", @inc);
133
134 # emulate MUA marking a Maildir message as read:
135 @fn = glob("$o/cur/*");
136 scalar(@fn) == 1 or xbail $lei_err, 'wrote multiple or zero files:', \@fn;
137 rename($fn[0], "$fn[0]S") or BAIL_OUT "rename $!";
138
139 lei_ok(qw(q -o), $o, 'bogus', \'clobber output dir to import keywords');
140 @fn = glob("$o/cur/*");
141 is_deeply(\@fn, [], 'output dir actually clobbered');
142 lei_ok('q', "m:$m", @inc);
143 my $res = json_utf8->decode($lei_out);
144 is_deeply($res->[0]->{kw}, ['seen'], 'seen flag set for external message')
145         or diag explain($res);
146 lei_ok('q', "m:$m", '--no-external');
147 is_deeply($res = json_utf8->decode($lei_out), [ undef ],
148         'external message not imported') or diag explain($res);
149
150 $o = "$ENV{HOME}/kwmboxrd";
151 lei_ok(qw(q -o), "mboxrd:$o", "m:$m", @inc);
152
153 # emulate MUA marking mboxrd message as unread
154 open my $fh, '<', $o or BAIL_OUT;
155 my $s = do { local $/; <$fh> };
156 $s =~ s/^Status: RO\n/Status: O\nX-Status: AF\n/sm or
157         fail "failed to clear R flag in $s";
158 open $fh, '>', $o or BAIL_OUT;
159 print $fh $s or BAIL_OUT;
160 close $fh or BAIL_OUT;
161
162 lei_ok(qw(q -o), "mboxrd:$o", 'm:bogus', @inc,
163         \'clobber mbox to import keywords');
164 lei_ok(qw(q -o), "mboxrd:$o", "m:$m", @inc);
165 open $fh, '<', $o or BAIL_OUT;
166 $s = do { local $/; <$fh> };
167 like($s, qr/^Status: O\nX-Status: AF\n/ms,
168         'seen keyword gone in mbox, answered + flagged set');
169
170 lei_ok(qw(q --pretty), "m:$m", @inc);
171 like($lei_out, qr/^  "kw": \["answered", "flagged"\],\n/sm,
172         '--pretty JSON output shows kw: on one line');
173
174 # ensure import on previously external-only message works
175 lei_ok('q', "m:$m");
176 is_deeply(json_utf8->decode($lei_out), [ undef ],
177         'to-be-imported message non-existent');
178 lei_ok(qw(import -F eml t/x-unknown-alpine.eml));
179 is($lei_err, '', 'no errors importing previous external-only message');
180 lei_ok('q', "m:$m");
181 $res = json_utf8->decode($lei_out);
182 is($res->[1], undef, 'got one result');
183 is_deeply($res->[0]->{kw}, [ qw(answered flagged) ], 'kw preserved on exact');
184
185 # ensure fuzzy match import works, too
186 $m = 'multipart@example.com';
187 $o = "$ENV{HOME}/fuzz";
188 lei_ok('q', '-o', $o, "m:$m", @inc);
189 @fn = glob("$o/cur/*");
190 scalar(@fn) == 1 or xbail $lei_err, "wrote multiple or zero files", \@fn;
191 rename($fn[0], "$fn[0]S") or BAIL_OUT "rename $!";
192 lei_ok('q', '-o', $o, "m:$m");
193 is_deeply([glob("$o/cur/*")], [], 'clobbered output results');
194 my $eml = eml_load('t/plack-2-txt-bodies.eml');
195 $eml->header_set('List-Id', '<list.example.com>');
196 my $in = $eml->as_string;
197 lei_ok([qw(import -F eml --stdin)], undef, { 0 => \$in, %$lei_opt });
198 is($lei_err, '', 'no errors from import');
199 lei_ok(qw(q -f mboxrd), "m:$m");
200 open $fh, '<', \$lei_out or BAIL_OUT $!;
201 my @res;
202 PublicInbox::MboxReader->mboxrd($fh, sub { push @res, shift });
203 is($res[0]->header('Status'), 'RO', 'seen kw set');
204 $res[0]->header_set('Status');
205 is_deeply(\@res, [ $eml ], 'imported message matches w/ List-Id');
206
207 $eml->header_set('List-Id', '<another.example.com>');
208 $in = $eml->as_string;
209 lei_ok([qw(import -F eml --stdin)], undef, { 0 => \$in, %$lei_opt });
210 is($lei_err, '', 'no errors from 2nd import');
211 lei_ok(qw(q -f mboxrd), "m:$m", 'l:another.example.com');
212 my @another;
213 open $fh, '<', \$lei_out or BAIL_OUT $!;
214 PublicInbox::MboxReader->mboxrd($fh, sub { push @another, shift });
215 is($another[0]->header('Status'), 'RO', 'seen kw set');
216
217 # forwarded
218 {
219         local $ENV{DBG} = 1;
220         $o = "$ENV{HOME}/forwarded";
221         lei_ok(qw(q -o), $o, "m:$m");
222         my @p = glob("$o/cur/*");
223         scalar(@p) == 1 or xbail('multiple when 1 expected', \@p);
224         my $passed = $p[0];
225         $passed =~ s/,S\z/,PS/ or xbail "failed to replace $passed";
226         rename($p[0], $passed) or xbail "rename $!";
227         lei_ok(qw(q -o), $o, 'm:bogus', \'clobber maildir');
228         is_deeply([glob("$o/cur/*")], [], 'old results clobbered');
229         lei_ok(qw(q -o), $o, "m:$m");
230         @p = glob("$o/cur/*");
231         scalar(@p) == 1 or xbail('multiple when 1 expected', \@p);
232         like($p[0], qr/,PS/, 'passed (Forwarded) flag kept');
233         lei_ok(qw(q -o), "mboxrd:$o.mboxrd", "m:$m");
234         open $fh, '<', "$o.mboxrd" or xbail $!;
235         my @res;
236         PublicInbox::MboxReader->mboxrd($fh, sub { push @res, shift });
237         scalar(@res) == 1 or xbail('multiple when 1 expected', \@res);
238         is($res[0]->header('Status'), 'RO', 'seen kw set');
239         is($res[0]->header('X-Status'), undef, 'no X-Status');
240
241         lei_ok(qw(q -o), "mboxrd:$o.mboxrd", 'bogus-for-import-before');
242         lei_ok(qw(q -o), $o, "m:$m");
243         @p = glob("$o/cur/*");
244         scalar(@p) == 1 or xbail('multiple when 1 expected', \@p);
245         like($p[0], qr/,PS/, 'passed (Forwarded) flag still kept');
246 }
247
248 }); # test_lei
249 done_testing;