]> Sergey Matveev's repositories - public-inbox.git/blob - t/mda.t
tests: move t/common.perl to PublicInbox::TestCommon
[public-inbox.git] / t / mda.t
1 # Copyright (C) 2014-2019 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 use strict;
4 use warnings;
5 use Test::More;
6 use Email::MIME;
7 use Cwd qw(getcwd);
8 use PublicInbox::MID qw(mid2path);
9 use PublicInbox::Git;
10 use PublicInbox::TestCommon;
11 my ($tmpdir, $for_destroy) = tmpdir();
12 my $home = "$tmpdir/pi-home";
13 my $pi_home = "$home/.public-inbox";
14 my $pi_config = "$pi_home/config";
15 my $maindir = "$tmpdir/main.git";
16 my $main_bin = getcwd()."/t/main-bin";
17 my $main_path = "$main_bin:$ENV{PATH}"; # for spamc ham mock
18 my $fail_bin = getcwd()."/t/fail-bin";
19 my $fail_path = "$fail_bin:$ENV{PATH}"; # for spamc spam mock
20 my $addr = 'test-public@example.com';
21 my $cfgpfx = "publicinbox.test";
22 my $faildir = "$home/faildir/";
23 my $mime;
24 my $git = PublicInbox::Git->new($maindir);
25
26 my $fail_bad_header = sub ($$$) {
27         my ($good_rev, $msg, $in) = @_;
28         my @f = glob("$faildir/*/*");
29         unlink @f if @f;
30         my ($out, $err) = ("", "");
31         my $opt = { 0 => \$in, 1 => \$out, 2 => \$err };
32         local $ENV{PATH} = $main_path;
33         ok(run_script(['-mda'], undef, $opt),
34                 "no error on undeliverable ($msg)");
35         my $rev = $git->qx(qw(rev-list HEAD));
36         chomp $rev;
37         is($rev, $good_rev, "bad revision not commited ($msg)");
38         @f = glob("$faildir/*/*");
39         is(scalar @f, 1, "faildir written to");
40         [ $in, $out, $err ];
41 };
42
43 {
44         ok(-x "$main_bin/spamc",
45                 "spamc ham mock found (run in top of source tree");
46         ok(-x "$fail_bin/spamc",
47                 "spamc mock found (run in top of source tree");
48         is(1, mkdir($home, 0755), "setup ~/ for testing");
49         is(1, mkdir($pi_home, 0755), "setup ~/.public-inbox");
50         is(0, system(qw(git init -q --bare), $maindir), "git init (main)");
51
52         my %cfg = (
53                 "$cfgpfx.address" => $addr,
54                 "$cfgpfx.inboxdir" => $maindir,
55         );
56         while (my ($k,$v) = each %cfg) {
57                 is(0, system(qw(git config --file), $pi_config, $k, $v),
58                         "setup $k");
59         }
60 }
61
62 local $ENV{GIT_COMMITTER_NAME} = eval {
63         use PublicInbox::MDA;
64         use PublicInbox::Address;
65         use Encode qw/encode/;
66         my $mbox = 't/utf8.mbox';
67         open(my $fh, '<', $mbox) or die "failed to open mbox: $mbox\n";
68         my $str = eval { local $/; <$fh> };
69         close $fh;
70         my $msg = Email::MIME->new($str);
71
72         my $from = $msg->header('From');
73         my ($author) = PublicInbox::Address::names($from);
74         my ($email) = PublicInbox::Address::emails($from);
75         my $date = $msg->header('Date');
76
77         is('El&#233;anor',
78                 encode('us-ascii', my $tmp = $author, Encode::HTMLCREF),
79                 'HTML conversion is correct');
80         is($email, 'e@example.com', 'email parsed correctly');
81         is($date, 'Thu, 01 Jan 1970 00:00:00 +0000',
82                 'message date parsed correctly');
83         $author;
84 };
85 die $@ if $@;
86
87 {
88         my $good_rev;
89         local $ENV{PI_EMERGENCY} = $faildir;
90         local $ENV{HOME} = $home;
91         local $ENV{ORIGINAL_RECIPIENT} = $addr;
92         my $simple = Email::Simple->new(<<EOF);
93 From: Me <me\@example.com>
94 To: You <you\@example.com>
95 Cc: $addr
96 Message-Id: <blah\@example.com>
97 Subject: hihi
98 Date: Thu, 01 Jan 1970 00:00:00 +0000
99
100 EOF
101         my $in = $simple->as_string;
102
103         # ensure successful message delivery
104         {
105                 local $ENV{PATH} = $main_path;
106                 ok(run_script(['-mda'], undef, { 0 => \$in }));
107                 my $rev = $git->qx(qw(rev-list HEAD));
108                 like($rev, qr/\A[a-f0-9]{40}/, "good revision committed");
109                 chomp $rev;
110                 my $cmt = $git->cat_file($rev);
111                 like($$cmt, qr/^author Me <me\@example\.com> 0 \+0000\n/m,
112                         "author info set correctly");
113                 like($$cmt, qr/^committer test <test-public\@example\.com>/m,
114                         "committer info set correctly");
115                 $good_rev = $rev;
116         }
117
118         # ensure failures work, fail with bad spamc
119         {
120                 my @prev = <$faildir/new/*>;
121                 is(scalar @prev, 0 , "nothing in PI_EMERGENCY before");
122                 local $ENV{PATH} = $fail_path;
123                 ok(run_script(['-mda'], undef, { 0 => \$in }));
124                 my @revs = $git->qx(qw(rev-list HEAD));
125                 is(scalar @revs, 1, "bad revision not committed");
126                 my @new = <$faildir/new/*>;
127                 is(scalar @new, 1, "PI_EMERGENCY is written to");
128         }
129
130         $fail_bad_header->($good_rev, "bad recipient", <<"");
131 From: Me <me\@example.com>
132 To: You <you\@example.com>
133 Message-Id: <bad-recipient\@example.com>
134 Subject: hihi
135 Date: Thu, 01 Jan 1970 00:00:00 +0000
136
137         my $fail = $fail_bad_header->($good_rev, "duplicate Message-ID", <<"");
138 From: Me <me\@example.com>
139 To: You <you\@example.com>
140 Cc: $addr
141 Message-ID: <blah\@example.com>
142 Subject: hihi
143 Date: Thu, 01 Jan 1970 00:00:00 +0000
144
145         like($fail->[2], qr/CONFLICT/, "duplicate Message-ID message");
146
147         $fail_bad_header->($good_rev, "missing From:", <<"");
148 To: $addr
149 Message-ID: <missing-from\@example.com>
150 Subject: hihi
151 Date: Thu, 01 Jan 1970 00:00:00 +0000
152
153         $fail_bad_header->($good_rev, "short subject:", <<"");
154 To: $addr
155 From: cat\@example.com
156 Message-ID: <short-subject\@example.com>
157 Subject: a
158 Date: Thu, 01 Jan 1970 00:00:00 +0000
159
160         $fail_bad_header->($good_rev, "no date", <<"");
161 To: $addr
162 From: u\@example.com
163 Message-ID: <no-date\@example.com>
164 Subject: hihi
165
166         $fail_bad_header->($good_rev, "bad date", <<"");
167 To: $addr
168 From: u\@example.com
169 Message-ID: <bad-date\@example.com>
170 Subject: hihi
171 Date: deadbeef
172
173 }
174
175 # spam training
176 {
177         local $ENV{PI_EMERGENCY} = $faildir;
178         local $ENV{HOME} = $home;
179         local $ENV{ORIGINAL_RECIPIENT} = $addr;
180         local $ENV{PATH} = $main_path;
181         my $mid = 'spam-train@example.com';
182         my $simple = Email::Simple->new(<<EOF);
183 From: Spammer <spammer\@example.com>
184 To: You <you\@example.com>
185 Cc: $addr
186 Message-ID: <$mid>
187 Subject: this message will be trained as spam
188 Date: Thu, 01 Jan 1970 00:00:00 +0000
189
190 EOF
191         my $in = $simple->as_string;
192
193         {
194                 # deliver the spam message, first
195                 ok(run_script(['-mda'], undef, { 0 => \$in }));
196                 my $path = mid2path($mid);
197                 my $msg = $git->cat_file("HEAD:$path");
198                 like($$msg, qr/\Q$mid\E/, "message delivered");
199
200                 # now train it
201                 local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
202                 local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
203                 local $ENV{GIT_COMMITTER_NAME};
204                 delete $ENV{GIT_COMMITTER_NAME};
205                 ok(run_script(['-learn', 'spam'], undef, { 0 => $msg }),
206                         "no failure from learning spam");
207                 ok(run_script(['-learn', 'spam'], undef, { 0 => $msg }),
208                         "no failure from learning spam idempotently");
209         }
210 }
211
212 # train ham message
213 {
214         local $ENV{PI_EMERGENCY} = $faildir;
215         local $ENV{HOME} = $home;
216         local $ENV{ORIGINAL_RECIPIENT} = $addr;
217         local $ENV{PATH} = $main_path;
218         my $mid = 'ham-train@example.com';
219         my $simple = Email::Simple->new(<<EOF);
220 From: False-positive <hammer\@example.com>
221 To: You <you\@example.com>
222 Cc: $addr
223 Message-ID: <$mid>
224 Subject: this message will be trained as spam
225 Date: Thu, 01 Jan 1970 00:00:00 +0000
226
227 EOF
228         my $in = $simple->as_string;
229
230         # now train it
231         # these should be overridden
232         local $ENV{GIT_AUTHOR_EMAIL} = 'trainer@example.com';
233         local $ENV{GIT_COMMITTER_EMAIL} = 'trainer@example.com';
234
235         ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
236                 "learned ham without failure");
237         my $path = mid2path($mid);
238         my $msg = $git->cat_file("HEAD:$path");
239         like($$msg, qr/\Q$mid\E/, "ham message delivered");
240         ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
241                 "learned ham idempotently ");
242
243         # ensure trained email is filtered, too
244         my $html_body = "<html><body>hi</body></html>";
245         my $parts = [
246                 Email::MIME->create(
247                         attributes => {
248                                 content_type => 'text/html; charset=UTF-8',
249                                 encoding => 'base64',
250                         },
251                         body => $html_body,
252                 ),
253                 Email::MIME->create(
254                         attributes => {
255                                 content_type => 'text/plain',
256                                 encoding => 'quoted-printable',
257                         },
258                         body => 'hi = "bye"',
259                 )
260         ];
261         $mid = 'multipart-html-sucks@11';
262         $mime = Email::MIME->create(
263                 header_str => [
264                   From => 'a@example.com',
265                   Subject => 'blah',
266                   Cc => $addr,
267                   'Message-ID' => "<$mid>",
268                   'Content-Type' => 'multipart/alternative',
269                 ],
270                 parts => $parts,
271         );
272
273         {
274                 $in = $mime->as_string;
275                 ok(run_script(['-learn', 'ham'], undef, { 0 => \$in }),
276                         "learned ham without failure");
277                 my $path = mid2path($mid);
278                 $msg = $git->cat_file("HEAD:$path");
279                 like($$msg, qr/<\Q$mid\E>/, "ham message delivered");
280                 unlike($$msg, qr/<html>/i, '<html> filtered');
281         }
282 }
283
284 # List-ID based delivery
285 {
286         local $ENV{PI_EMERGENCY} = $faildir;
287         local $ENV{HOME} = $home;
288         local $ENV{ORIGINAL_RECIPIENT} = undef;
289         delete $ENV{ORIGINAL_RECIPIENT};
290         local $ENV{PATH} = $main_path;
291         my $list_id = 'foo.example.com';
292         my $mid = 'list-id-delivery@example.com';
293         my $simple = Email::Simple->new(<<EOF);
294 From: user <user\@example.com>
295 To: You <you\@example.com>
296 Cc: $addr
297 Message-ID: <$mid>
298 List-Id: <$list_id>
299 Subject: this message will be trained as spam
300 Date: Thu, 01 Jan 1970 00:00:00 +0000
301
302 EOF
303         system(qw(git config --file), $pi_config, "$cfgpfx.listid", $list_id);
304         $? == 0 or die "failed to set listid $?";
305         my $in = $simple->as_string;
306         ok(run_script(['-mda'], undef, { 0 => \$in }),
307                 'mda OK with List-Id match');
308         my $path = mid2path($mid);
309         my $msg = $git->cat_file("HEAD:$path");
310         like($$msg, qr/\Q$list_id\E/, 'delivered message w/ List-ID matches');
311
312         # try a message w/o precheck
313         $simple = Email::Simple->new(<<EOF);
314 To: You <you\@example.com>
315 List-Id: <$list_id>
316
317 this message would not be accepted without --no-precheck
318 EOF
319         $in = $simple->as_string;
320         my ($out, $err) = ('', '');
321         my $rdr = { 0 => \$in, 1 => \$out, 2 => \$err };
322         ok(run_script(['-mda', '--no-precheck'], undef, $rdr),
323                 'mda OK with List-Id match and --no-precheck');
324         my $cur = $git->qx(qw(diff HEAD~1..HEAD));
325         like($cur, qr/this message would not be accepted without --no-precheck/,
326                 '--no-precheck delivered message anyways');
327
328         # try a message with multiple List-ID headers
329         $in = <<EOF;
330 List-ID: <foo.bar>
331 List-ID: <$list_id>
332 Message-ID: <2lids\@example>
333 Subject: two List-IDs
334 From: user <user\@example.com>
335 To: $addr
336 Date: Fri, 02 Oct 1993 00:00:00 +0000
337
338 EOF
339         ($out, $err) = ('', '');
340         ok(run_script(['-mda'], undef, $rdr),
341                 'mda OK with multiple List-Id matches');
342         $cur = $git->qx(qw(diff HEAD~1..HEAD));
343         like($cur, qr/Message-ID: <2lids\@example>/,
344                 'multi List-ID match delivered');
345         like($err, qr/multiple List-ID/, 'warned about multiple List-ID');
346 }
347
348 done_testing();