public-inbox.git  about / heads / tags
an "archives first" approach to mailing lists
blob 8c981c6cc593e78252335ac2c9bfdfcfbd4e1e03 6494 bytes (raw)
$ git show HEAD:t/psgi_search.t	# shows this blob on the CLI

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
 
#!perl -w
# Copyright (C) all contributors <meta@public-inbox.org>
# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
use v5.12;
use PublicInbox::TestCommon;
use IO::Uncompress::Gunzip qw(gunzip);
use PublicInbox::Eml;
use PublicInbox::Inbox;
my @mods = qw(DBD::SQLite Xapian HTTP::Request::Common Plack::Test
		URI::Escape Plack::Builder);
require_mods(@mods);
use_ok($_) for (qw(HTTP::Request::Common Plack::Test));
use_ok 'PublicInbox::WWW';
use_ok 'PublicInbox::SearchIdx';
my ($tmpdir, $for_destroy) = tmpdir();
local $ENV{TZ} = 'UTC';

my $digits = '10010260936330';
my $ua = 'Pine.LNX.4.10';
my $mid = "$ua.$digits.2460-100000\@penguin.transmeta.com";
my $ibx = create_inbox '26-git', indexlevel => 'full', tmpdir => "$tmpdir/1",
sub {
	my ($im) = @_;
	# n.b. these headers are not properly RFC2047-encoded
	$im->add(PublicInbox::Eml->new(<<EOF)) or BAIL_OUT;
Subject: test Ævar
Message-ID: <$mid>
From: Ævar Arnfjörð Bjarmason <avarab\@example>
To: git\@vger.kernel.org

EOF

	$im->add(PublicInbox::Eml->new(<<"")) or BAIL_OUT;
Message-ID: <reply\@asdf>
From: replier <r\@example.com>
In-Reply-To: <$mid>
Subject: mismatch

	$im->add(PublicInbox::Eml->new(<<'EOF')) or BAIL_OUT;
Subject:
Message-ID: <blank-subject@example.com>
From: blank subject <blank-subject@example.com>
To: git@vger.kernel.org

EOF

	$im->add(PublicInbox::Eml->new(<<'EOF')) or BAIL_OUT;
Message-ID: <no-subject-at-all@example.com>
From: no subject at all <no-subject-at-all@example.com>
To: git@vger.kernel.org

EOF
	$im->add(PublicInbox::Eml->new(<<'EOF')) or BAIL_OUT;
Message-ID: <ampersand@example.com>
From: <e@example.com>
To: git@vger.kernel.org
Subject: git & ampersand

hi +++ b/foo
x=y
s'more

EOF
};

my $cfg = cfg_new $tmpdir, <<EOF;
[publicinbox "test"]
	address = git\@vger.kernel.org
	inboxdir = $ibx->{inboxdir}
EOF
my $www = PublicInbox::WWW->new($cfg);
test_psgi(sub { $www->call(@_) }, sub {
	my ($cb) = @_;
	my ($html, $res);
	my $approxidate = 'now';
	for my $req ('/test/?q=%C3%86var', '/test/?q=%25C3%2586var') {
		$res = $cb->(GET($req."+d:..$approxidate"));
		$html = $res->content;
		like($html, qr/<title>&#198;var d:\.\.\Q$approxidate\E/,
			'HTML escaped in title, "d:..$APPROXIDATE" preserved');
		my @res = ($html =~ m/\?q=(.+var)\+d:\.\.\Q$approxidate\E/g);
		ok(scalar(@res), 'saw query strings');
		my %uniq = map { $_ => 1 } @res;
		is(1, scalar keys %uniq, 'all query values identical in HTML');
		is('%C3%86var', (keys %uniq)[0], 'matches original query');
		ok(index($html, 'by &#198;var Arnfj&#246;r&#240; Bjarmason')
			>= 0, "displayed Ævar's name properly in HTML");
		like($html, qr/download mbox\.gz: .*?"full threads"/s,
			'"full threads" download option shown');
	}
	like($html, qr/Initial query\b.*?returned no.results, used:.*instead/s,
		'noted retry on double-escaped query {-uxs_retried}');

	my $warn = [];
	local $SIG{__WARN__} = sub { push @$warn, @_ };
	$res = $cb->(GET('/test/?q=s:test&l=5e'));
	is($res->code, 200, 'successful search result');
	is_deeply([], $warn, 'no warnings from non-numeric comparison');

	$res = $cb->(GET('/test/?&q=s:test'));
	is($res->code, 200, 'successful search result');
	is_deeply([], $warn, 'no warnings from black parameter');

	$res = $cb->(POST('/test/?q=s:bogus&x=m'));
	is($res->code, 404, 'failed search result gives 404');
	like($res->content, qr/No results found/, "`No results' shown");
	is_deeply([], $warn, 'no warnings');

	my $mid_re = qr/\Q$mid\E/o;
	while (length($digits) > 8) {
		$res = $cb->(GET("/test/$ua.$digits/"));
		is($res->code, 300, 'partial match found while truncated');
		like($res->content, qr/\b1 partial match found\b/);
		like($res->content, $mid_re, 'found mid in response');
		chop($digits);
	}
	$res = $cb->(GET("/test/$mid/"));
	$html = $res->content;
	like($html, qr/\bFrom: &#198;var /,
		"displayed Ævar's name properly in permalink From:");
	unlike($html, qr/&#195;/, 'no raw octets in permalink HTML');

	$res = $cb->(GET('/test/'));
	$html = $res->content;
	like($html, qr/\bhref="no-subject-at-all[^>]+>\(no subject\)</,
		'subject-less message linked from "/$INBOX/"');
	like($html, qr/\bhref="blank-subject[^>]+>\(no subject\)</,
		'blank subject message linked from "/$INBOX/"');
	like($html, qr/test &#198;var/,
		"displayed Ævar's name properly in topic view");

	$res = $cb->(GET('/test/?q=tc:git'));
	like($html, qr/\bhref="no-subject-at-all[^>]+>\(no subject\)</,
		'subject-less message linked from "/$INBOX/?q=..."');
	like($html, qr/\bhref="blank-subject[^>]+>\(no subject\)</,
		'blank subject message linked from "/$INBOX/?q=..."');
	$res = $cb->(GET('/test/no-subject-at-all@example.com/raw'));
	like($res->header('Content-Disposition'),
		qr/filename=no-subject\.txt/);
	$res = $cb->(GET('/test/no-subject-at-all@example.com/t.mbox.gz'));
	like($res->header('Content-Disposition'),
		qr/filename=no-subject\.mbox\.gz/);

	# "full threads" mbox.gz download
	$res = $cb->(POST("/test/?q=s:test+d:..$approxidate&x=m&t"));
	is($res->code, 200, 'successful mbox download with threads');
	gunzip(\($res->content) => \(my $before));
	is_deeply([ "Message-ID: <$mid>\n", "Message-ID: <reply\@asdf>\n" ],
		[ grep(/^Message-ID:/m, split(/^/m, $before)) ],
		'got full thread');

	# clobber has_threadid to emulate old versions:
	{
		my $sidx = PublicInbox::SearchIdx->new($ibx, 0);
		my $xdb = $sidx->idx_acquire;
		$xdb->set_metadata('has_threadid', '0');
		$sidx->idx_release;
	}
	$cfg->each_inbox(sub { delete $_[0]->{search} });
	$res = $cb->(GET('/test/?q=s:test'));
	is($res->code, 200, 'successful search w/o has_threadid');
	unlike($html, qr/download mbox\.gz: .*?"full threads"/s,
		'"full threads" download option not shown w/o has_threadid');

	# in case somebody uses curl to bypass <form>
	$res = $cb->(POST("/test/?q=s:test+d:..$approxidate&x=m&t"));
	is($res->code, 200, 'successful mbox download w/ threads');
	gunzip(\($res->content) => \(my $after));
	isnt($before, $after);

	$res = $cb->(GET('/test/?q=git+%26+ampersand&x=A'));
	is $res->code, 200, 'Atom hit with ampersand';
	unlike $res->content, qr/git\+&\+ampersand/, '& is HTML-escaped';

	$res = $cb->(GET('/test/?q=%22hi+%2b%2b%2b+b/foo%22&x=A'));
	is $res->code, 200, 'slashes and plusses search hit';
	like $res->content, qr!q=%22hi\+(?:%2[bB]){3}\+b/foo%22!,
		'+ and " escaped, but slash not escaped in query';

	$res = $cb->(GET(q{/test/?q=%22s'more%22&x=A}));
	is $res->code, 200, 'single quote inside phrase';
	# TODO: more tests and odd cases
});

done_testing();

git clone https://public-inbox.org/public-inbox.git
git clone http://7fh6tueqddpjyxjmgtdiueylzoqt6pt7hec3pukyptlmohoowvhde4yd.onion/public-inbox.git