]> Sergey Matveev's repositories - public-inbox.git/blob - t/plack.t
update copyrights for 2021
[public-inbox.git] / t / plack.t
1 # Copyright (C) 2014-2021 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 xsys_e(qw(git config -f), $pi_config,
25         qw(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         $res = $cb->(GET($pfx . '/blah@example.com/t.mbox.gz'));
214         is(501, $res->code, '501 when overview missing');
215         like($res->content, qr!\bOverview\b!, 'overview omission noted');
216 });
217
218 # legacy redirects
219 foreach my $t (qw(m f)) {
220         test_psgi($app, sub {
221                 my ($cb) = @_;
222                 my $res = $cb->(GET($pfx . "/$t/blah\@example.com.txt"));
223                 is(301, $res->code, "redirect for old $t .txt link");
224                 my $location = $res->header('Location');
225                 like($location, qr!/blah\@example\.com/raw\z!,
226                         ".txt redirected to /raw");
227         });
228 }
229
230 my %umap = (
231         'm' => '',
232         'f' => '',
233         't' => 't/',
234 );
235 while (my ($t, $e) = each %umap) {
236         test_psgi($app, sub {
237                 my ($cb) = @_;
238                 my $res = $cb->(GET($pfx . "/$t/blah\@example.com.html"));
239                 is(301, $res->code, "redirect for old $t .html link");
240                 my $location = $res->header('Location');
241                 like($location,
242                         qr!/blah\@example\.com/$e(?:#u)?\z!,
243                         ".html redirected to new location");
244         });
245 }
246 foreach my $sfx (qw(mbox mbox.gz)) {
247         test_psgi($app, sub {
248                 my ($cb) = @_;
249                 my $res = $cb->(GET($pfx . "/t/blah\@example.com.$sfx"));
250                 is(301, $res->code, 'redirect for old thread link');
251                 my $location = $res->header('Location');
252                 like($location,
253                      qr!/blah\@example\.com/t\.mbox(?:\.gz)?\z!,
254                      "$sfx redirected to /mbox.gz");
255         });
256 }
257 test_psgi($app, sub {
258         my ($cb) = @_;
259         # for a while, we used to support /$INBOX/$X40/
260         # when we "compressed" long Message-IDs to SHA-1
261         # Now we're stuck supporting them forever :<
262         foreach my $path (@ls) {
263                 $path =~ tr!/!!d;
264                 my $from = "http://example.com/test/$path/";
265                 my $res = $cb->(GET($from));
266                 is(301, $res->code, 'is permanent redirect');
267                 like($res->header('Location'),
268                         qr!/test/blah\@example\.com/!,
269                         'redirect from x40 MIDs works');
270         }
271 });
272
273 # dumb HTTP clone/fetch support
274 test_psgi($app, sub {
275         my ($cb) = @_;
276         my $path = '/test/info/refs';
277         my $req = HTTP::Request->new('GET' => $path);
278         my $res = $cb->($req);
279         is(200, $res->code, 'refs readable');
280         my $orig = $res->content;
281
282         $req->header('Range', 'bytes=5-10');
283         $res = $cb->($req);
284         is(206, $res->code, 'got partial response');
285         is($res->content, substr($orig, 5, 6), 'partial body OK');
286
287         $req->header('Range', 'bytes=5-');
288         $res = $cb->($req);
289         is(206, $res->code, 'got partial another response');
290         is($res->content, substr($orig, 5), 'partial body OK past end');
291 });
292
293 # things which should fail
294 test_psgi($app, sub {
295         my ($cb) = @_;
296
297         my $res = $cb->(PUT('/'));
298         is(405, $res->code, 'no PUT to / allowed');
299         $res = $cb->(PUT('/test/'));
300         is(405, $res->code, 'no PUT /$INBOX allowed');
301
302         # TODO
303         # $res = $cb->(GET('/'));
304 });
305
306 done_testing();