]> Sergey Matveev's repositories - public-inbox.git/blob - t/plack.t
remove most internal Email::MIME usage
[public-inbox.git] / t / plack.t
1 # Copyright (C) 2014-2020 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 PublicInbox::TestCommon;
7 my $psgi = "./examples/public-inbox.psgi";
8 my ($tmpdir, $for_destroy) = tmpdir();
9 my $pi_config = "$tmpdir/config";
10 my $inboxdir = "$tmpdir/main.git";
11 my $addr = 'test-public@example.com';
12 my @mods = qw(HTTP::Request::Common Plack::Test URI::Escape);
13 require_mods(@mods);
14 use_ok 'PublicInbox::Import';
15 use_ok 'PublicInbox::Git';
16 my @ls;
17
18 foreach my $mod (@mods) { use_ok $mod; }
19 local $ENV{PI_CONFIG} = $pi_config;
20 ok(-f $psgi, "psgi example file found");
21 my $pfx = 'http://example.com/test';
22 ok(run_script(['-init', 'test', $inboxdir, "$pfx/", $addr]),
23         'initialized repo');
24 PublicInbox::Import::run_die([qw(git config -f), $pi_config,
25         'publicinbox.test.newsgroup', 'inbox.test']);
26 open my $fh, '>', "$inboxdir/description" or die "open: $!\n";
27 print $fh "test for public-inbox\n";
28 close $fh or die "close: $!\n";
29 my $app = require $psgi;
30 my $git = PublicInbox::Git->new($inboxdir);
31 my $im = PublicInbox::Import->new($git, 'test', $addr);
32 # ensure successful message delivery
33 {
34         my $mime = PublicInbox::Eml->new(<<EOF);
35 From: Me <me\@example.com>
36 To: You <you\@example.com>
37 Cc: $addr
38 Message-Id: <blah\@example.com>
39 Subject: hihi
40 Date: Fri, 02 Oct 1993 00:00:00 +0000
41
42 > quoted text
43 zzzzzz
44 EOF
45         $im->add($mime);
46         $im->done;
47         my $rev = $git->qx(qw(rev-list HEAD));
48         like($rev, qr/\A[a-f0-9]{40}/, "good revision committed");
49         @ls = $git->qx(qw(ls-tree -r --name-only HEAD));
50         chomp @ls;
51
52         # multipart with two text bodies
53         $mime = eml_load 't/plack-2-txt-bodies.eml';
54         $im->add($mime);
55
56         # multipart with attached patch + filename
57         $mime = eml_load 't/plack-attached-patch.eml';
58         $im->add($mime);
59
60         # multipart collapsed to single quoted-printable text/plain
61         $mime = eml_load 't/plack-qp.eml';
62         like($mime->body_raw, qr/hi =3D bye=/, 'our test used QP correctly');
63         $im->add($mime);
64
65         my $crlf = <<EOF;
66 From: Me
67   <me\@example.com>
68 To: $addr
69 Message-Id: <crlf\@example.com>
70 Subject: carriage
71   return
72   in
73   long
74   subject
75 Date: Fri, 02 Oct 1993 00:00:00 +0000
76
77 :(
78 EOF
79         $crlf =~ s/\n/\r\n/sg;
80         $im->add(PublicInbox::Eml->new($crlf));
81
82         $im->done;
83 }
84
85 test_psgi($app, sub {
86         my ($cb) = @_;
87         foreach my $u (qw(robots.txt favicon.ico .well-known/foo)) {
88                 my $res = $cb->(GET("http://example.com/$u"));
89                 is($res->code, 404, "$u is missing");
90         }
91 });
92
93 test_psgi($app, sub {
94         my ($cb) = @_;
95         my $res = $cb->(GET('http://example.com/test/crlf@example.com/'));
96         is($res->code, 200, 'retrieved CRLF as HTML');
97         unlike($res->content, qr/\r/, 'no CR in HTML');
98         $res = $cb->(GET('http://example.com/test/crlf@example.com/raw'));
99         is($res->code, 200, 'retrieved CRLF raw');
100         like($res->content, qr/\r/, 'CR preserved in raw message');
101 });
102
103 # redirect with newsgroup
104 test_psgi($app, sub {
105         my ($cb) = @_;
106         my $from = 'http://example.com/inbox.test';
107         my $to = 'http://example.com/test/';
108         my $res = $cb->(GET($from));
109         is($res->code, 301, 'newsgroup name is permanent redirect');
110         is($to, $res->header('Location'), 'redirect location matches');
111         $from .= '/';
112         is($res->code, 301, 'newsgroup name/ is permanent redirect');
113         is($to, $res->header('Location'), 'redirect location matches');
114 });
115
116 # redirect with trailing /
117 test_psgi($app, sub {
118         my ($cb) = @_;
119         my $from = 'http://example.com/test';
120         my $to = "$from/";
121         my $res = $cb->(GET($from));
122         is(301, $res->code, 'is permanent redirect');
123         is($to, $res->header('Location'),
124                 'redirect location matches with trailing slash');
125 });
126
127 foreach my $t (qw(t T)) {
128         test_psgi($app, sub {
129                 my ($cb) = @_;
130                 my $u = $pfx . "/blah\@example.com/$t";
131                 my $res = $cb->(GET($u));
132                 is(301, $res->code, "redirect for missing /");
133                 my $location = $res->header('Location');
134                 like($location, qr!/\Q$t\E/#u\z!,
135                         'redirected with missing /');
136         });
137 }
138 foreach my $t (qw(f)) {
139         test_psgi($app, sub {
140                 my ($cb) = @_;
141                 my $u = $pfx . "/blah\@example.com/$t";
142                 my $res = $cb->(GET($u));
143                 is(301, $res->code, "redirect for legacy /f");
144                 my $location = $res->header('Location');
145                 like($location, qr!/blah\@example\.com/\z!,
146                         'redirected with missing /');
147         });
148 }
149
150 test_psgi($app, sub {
151         my ($cb) = @_;
152         my $atomurl = 'http://example.com/test/new.atom';
153         my $res = $cb->(GET('http://example.com/test/new.html'));
154         is(200, $res->code, 'success response received');
155         like($res->content, qr!href="new\.atom"!,
156                 'atom URL generated');
157         like($res->content, qr!href="blah\@example\.com/"!,
158                 'index generated');
159         like($res->content, qr!1993-10-02!, 'date set');
160 });
161
162 test_psgi($app, sub {
163         my ($cb) = @_;
164         my $res = $cb->(GET($pfx . '/atom.xml'));
165         is(200, $res->code, 'success response received for atom');
166         my $body = $res->content;
167         like($body, qr!link\s+href="\Q$pfx\E/blah\@example\.com/"!s,
168                 'atom feed generated correct URL');
169         like($body, qr/<title>test for public-inbox/,
170                 "set title in XML feed");
171         like($body, qr/zzzzzz/, 'body included');
172         $res = $cb->(GET($pfx . '/description'));
173         like($res->content, qr/test for public-inbox/, 'got description');
174 });
175
176 test_psgi($app, sub {
177         my ($cb) = @_;
178         my $path = '/blah@example.com/';
179         my $res = $cb->(GET($pfx . $path));
180         is(200, $res->code, "success for $path");
181         my $html = $res->content;
182         like($html, qr!<title>hihi - Me</title>!, 'HTML returned');
183         like($html, qr!<a\nhref="raw"!s, 'raw link present');
184         like($html, qr!&gt; quoted text!s, 'quoted text inline');
185
186         $path .= 'f/';
187         $res = $cb->(GET($pfx . $path));
188         is(301, $res->code, "redirect for $path");
189         my $location = $res->header('Location');
190         like($location, qr!/blah\@example\.com/\z!,
191                 '/$MESSAGE_ID/f/ redirected to /$MESSAGE_ID/');
192
193         $res = $cb->(GET($pfx . '/multipart@example.com/'));
194         like($res->content,
195                 qr/hi\n.*-- Attachment #2.*\nbye\n/s, 'multipart split');
196
197         $res = $cb->(GET($pfx . '/patch@example.com/'));
198         $html = $res->content;
199         like($html, qr!see attached!, 'original body');
200         like($html, qr!.*Attachment #2: foo&(?:amp|#38);\.patch --!,
201                 'parts split with filename');
202
203         $res = $cb->(GET($pfx . '/qp@example.com/'));
204         like($res->content, qr/\bhi = bye\b/, "HTML output decoded QP");
205 });
206
207 test_psgi($app, sub {
208         my ($cb) = @_;
209         my $res = $cb->(GET($pfx . '/blah@example.com/raw'));
210         is(200, $res->code, 'success response received for /*/raw');
211         like($res->content, qr!^From !sm, "mbox returned");
212 });
213
214 # legacy redirects
215 foreach my $t (qw(m f)) {
216         test_psgi($app, sub {
217                 my ($cb) = @_;
218                 my $res = $cb->(GET($pfx . "/$t/blah\@example.com.txt"));
219                 is(301, $res->code, "redirect for old $t .txt link");
220                 my $location = $res->header('Location');
221                 like($location, qr!/blah\@example\.com/raw\z!,
222                         ".txt redirected to /raw");
223         });
224 }
225
226 my %umap = (
227         'm' => '',
228         'f' => '',
229         't' => 't/',
230 );
231 while (my ($t, $e) = each %umap) {
232         test_psgi($app, sub {
233                 my ($cb) = @_;
234                 my $res = $cb->(GET($pfx . "/$t/blah\@example.com.html"));
235                 is(301, $res->code, "redirect for old $t .html link");
236                 my $location = $res->header('Location');
237                 like($location,
238                         qr!/blah\@example\.com/$e(?:#u)?\z!,
239                         ".html redirected to new location");
240         });
241 }
242 foreach my $sfx (qw(mbox mbox.gz)) {
243         test_psgi($app, sub {
244                 my ($cb) = @_;
245                 my $res = $cb->(GET($pfx . "/t/blah\@example.com.$sfx"));
246                 is(301, $res->code, 'redirect for old thread link');
247                 my $location = $res->header('Location');
248                 like($location,
249                      qr!/blah\@example\.com/t\.mbox(?:\.gz)?\z!,
250                      "$sfx redirected to /mbox.gz");
251         });
252 }
253 test_psgi($app, sub {
254         my ($cb) = @_;
255         # for a while, we used to support /$INBOX/$X40/
256         # when we "compressed" long Message-IDs to SHA-1
257         # Now we're stuck supporting them forever :<
258         foreach my $path (@ls) {
259                 $path =~ tr!/!!d;
260                 my $from = "http://example.com/test/$path/";
261                 my $res = $cb->(GET($from));
262                 is(301, $res->code, 'is permanent redirect');
263                 like($res->header('Location'),
264                         qr!/test/blah\@example\.com/!,
265                         'redirect from x40 MIDs works');
266         }
267 });
268
269 # dumb HTTP clone/fetch support
270 test_psgi($app, sub {
271         my ($cb) = @_;
272         my $path = '/test/info/refs';
273         my $req = HTTP::Request->new('GET' => $path);
274         my $res = $cb->($req);
275         is(200, $res->code, 'refs readable');
276         my $orig = $res->content;
277
278         $req->header('Range', 'bytes=5-10');
279         $res = $cb->($req);
280         is(206, $res->code, 'got partial response');
281         is($res->content, substr($orig, 5, 6), 'partial body OK');
282
283         $req->header('Range', 'bytes=5-');
284         $res = $cb->($req);
285         is(206, $res->code, 'got partial another response');
286         is($res->content, substr($orig, 5), 'partial body OK past end');
287 });
288
289 # things which should fail
290 test_psgi($app, sub {
291         my ($cb) = @_;
292
293         my $res = $cb->(PUT('/'));
294         is(405, $res->code, 'no PUT to / allowed');
295         $res = $cb->(PUT('/test/'));
296         is(405, $res->code, 'no PUT /$INBOX allowed');
297
298         # TODO
299         # $res = $cb->(GET('/'));
300 });
301
302 done_testing();