-# Copyright (C) 2015 all contributors <meta@public-inbox.org>
-# License: AGPLv3 or later (https://www.gnu.org/licenses/agpl-3.0.txt)
+# Copyright (C) 2015-2018 all contributors <meta@public-inbox.org>
+# License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
use strict;
use warnings;
use Test::More;
-eval { require PublicInbox::SearchIdx };
-plan skip_all => "Xapian missing for nntpd" if $@;
-eval { require PublicInbox::Msgmap };
-plan skip_all => "DBD::SQLite missing for nntpd" if $@;
+foreach my $mod (qw(DBD::SQLite Search::Xapian Danga::Socket)) {
+ eval "require $mod";
+ plan skip_all => "$mod missing for nntpd.t" if $@;
+}
+require PublicInbox::SearchIdx;
+require PublicInbox::Msgmap;
use Cwd;
use Email::Simple;
use IO::Socket;
use Socket qw(SO_KEEPALIVE IPPROTO_TCP TCP_NODELAY);
use File::Temp qw/tempdir/;
use Net::NNTP;
-use IPC::Run qw(run);
-use Data::Dumper;
-my $tmpdir = tempdir(CLEANUP => 1);
+my $tmpdir = tempdir('pi-nntpd-XXXXXX', TMPDIR => 1, CLEANUP => 1);
my $home = "$tmpdir/pi-home";
my $err = "$tmpdir/stderr.log";
my $out = "$tmpdir/stdout.log";
-my $pi_home = "$home/.public-inbox";
-my $pi_config = "$pi_home/config";
-my $maindir = "$tmpdir/main.git";
-my $main_bin = getcwd()."/t/main-bin";
-my $main_path = "$main_bin:$ENV{PATH}"; # for spamc ham mock
+my $mainrepo = "$tmpdir/main.git";
my $group = 'test-nntpd';
my $addr = $group . '@example.com';
-my $cfgpfx = "publicinbox.$group";
-my $failbox = "$home/fail.mbox";
-local $ENV{PI_EMERGENCY} = $failbox;
-my $mda = 'blib/script/public-inbox-mda';
my $nntpd = 'blib/script/public-inbox-nntpd';
my $init = 'blib/script/public-inbox-init';
-my $index = 'blib/script/public-inbox-index';
+use_ok 'PublicInbox::Import';
+use_ok 'PublicInbox::Inbox';
+use_ok 'PublicInbox::Git';
+use_ok 'PublicInbox::V2Writable';
+# XXX FIXME: make it easier to test both versions
+my $version = int($ENV{PI_VERSION} || 1);
my %opts = (
LocalAddr => '127.0.0.1',
ReuseAddr => 1,
);
my $sock = IO::Socket::INET->new(%opts);
my $pid;
+my $len;
END { kill 'TERM', $pid if defined $pid };
+
+my $ibx = {
+ mainrepo => $mainrepo,
+ name => $group,
+ version => $version,
+ -primary_address => $addr,
+};
+$ibx = PublicInbox::Inbox->new($ibx);
{
local $ENV{HOME} = $home;
- system($init, $group, $maindir, 'http://example.com/', $addr);
+ my @cmd = ($init, $group, $mainrepo, 'http://example.com/', $addr);
+ push @cmd, "-V$version";
+ is(system(@cmd), 0, 'init OK');
+ is(system(qw(git config), "--file=$home/.public-inbox/config",
+ "publicinbox.$group.newsgroup", $group),
+ 0, 'enabled newsgroup');
+ my $len;
+
+ my $im;
+ if ($version == 2) {
+ $im = PublicInbox::V2Writable->new($ibx);
+ } elsif ($version == 1) {
+ my $git = PublicInbox::Git->new($mainrepo);
+ $im = PublicInbox::Import->new($git, 'test', $addr);
+ } else {
+ die "unsupported version: $version";
+ }
# ensure successful message delivery
{
- local $ENV{ORIGINAL_RECIPIENT} = $addr;
- my $simple = Email::Simple->new(<<EOF);
-From: Me <me\@example.com>
-To: You <you\@example.com>
+ my $mime = Email::MIME->new(<<EOF);
+To: =?utf-8?Q?El=C3=A9anor?= <you\@example.com>
+From: =?utf-8?Q?El=C3=A9anor?= <me\@example.com>
Cc: $addr
Message-Id: <nntp\@example.com>
-Subject: hihi
+Content-Type: text/plain; charset=utf-8
+Subject: Testing for =?utf-8?Q?El=C3=A9anor?=
Date: Thu, 01 Jan 1970 06:06:06 +0000
+Content-Transfer-Encoding: 8bit
-nntp
+This is a test message for El\xc3\xa9anor
EOF
- my $in = $simple->as_string;
- local $ENV{PATH} = $main_path;
- IPC::Run::run([$mda], \$in);
- is(0, $?, 'ran MDA correctly');
- is(0, system($index, $maindir), 'indexed git dir');
+ my $list_id = $addr;
+ $list_id =~ s/@/./;
+ $mime->header_set('List-Id', "<$list_id>");
+ $len = length($mime->as_string);
+ $im->add($mime);
+ $im->done;
+ if ($version == 1) {
+ my $s = PublicInbox::SearchIdx->new($mainrepo, 1);
+ $s->index_sync;
+ }
}
ok($sock, 'sock created');
my $list = $n->list;
is_deeply($list, { $group => [ qw(1 1 n) ] }, 'LIST works');
is_deeply([$n->group($group)], [ qw(0 1 1), $group ], 'GROUP works');
+ is_deeply($n->listgroup($group), [1], 'listgroup OK');
%opts = (
PeerAddr => $host_port,
my $mid = '<nntp@example.com>';
my %xhdr = (
'message-id' => $mid,
- 'subject' => 'hihi',
+ subject => "Testing for El\xc3\xa9anor",
'date' => 'Thu, 01 Jan 1970 06:06:06 +0000',
- 'from' => 'Me <me@example.com>',
- 'to' => 'You <you@example.com>',
+ 'from' => "El\xc3\xa9anor <me\@example.com>",
+ 'to' => "El\xc3\xa9anor <you\@example.com>",
'cc' => $addr,
'xref' => "example.com $group:1"
);
is($buf, "201 server ready - post via email\r\n", 'got greeting');
$s->autoflush(1);
+ syswrite($s, "NEWGROUPS\t19990424 000000 \033GMT\007\r\n");
+ is(0, sysread($s, $buf, 4096), 'GOT EOF on cntrl');
+
+ $s = IO::Socket::INET->new(%opts);
+ sysread($s, $buf, 4096);
+ is($buf, "201 server ready - post via email\r\n", 'got greeting');
+ $s->autoflush(1);
+
+ syswrite($s, "NEWGROUPS 19990424 000000 GMT\r\n");
+ $buf = read_til_dot($s);
+ like($buf, qr/\A231 list of /, 'newgroups OK');
+
while (my ($k, $v) = each %xhdr) {
is_deeply($n->xhdr("$k $mid"), { $mid => $v },
"XHDR $k by message-id works");
"$k by article number works");
is_deeply($n->xhdr("$k 1-"), { 1 => $v },
"$k by article range works");
- next;
$buf = '';
syswrite($s, "HDR $k $mid\r\n");
- do {
- sysread($s, $buf, 4096, length($buf));
- } until ($buf =~ /^[^2]../ || $buf =~ /\r\n\.\r\n\z/);
+ $buf = read_til_dot($s);
my @r = split("\r\n", $buf);
- like($r[0], qr/\A224 /, '224 response for HDR');
+ like($r[0], qr/\A225 /, '225 response for HDR');
is($r[1], "0 $v", 'got expected response for HDR');
}
}
is_deeply($n->xover('1-'), {
- '1' => ['hihi',
- 'Me <me@example.com>',
+ '1' => ["Testing for El\xc3\xa9anor",
+ "El\xc3\xa9anor <me\@example.com>",
'Thu, 01 Jan 1970 06:06:06 +0000',
'<nntp@example.com>',
'',
- '202',
+ $len,
'1' ] }, "XOVER range works");
is_deeply($n->xover('1'), {
- '1' => ['hihi',
- 'Me <me@example.com>',
+ '1' => ["Testing for El\xc3\xa9anor",
+ "El\xc3\xa9anor <me\@example.com>",
'Thu, 01 Jan 1970 06:06:06 +0000',
'<nntp@example.com>',
'',
- '202',
+ $len,
'1' ] }, "XOVER by article works");
+ is_deeply($n->head(1), $n->head('<nntp@example.com>'), 'HEAD OK');
+ is_deeply($n->body(1), $n->body('<nntp@example.com>'), 'BODY OK');
+ is($n->body(1)->[0], "This is a test message for El\xc3\xa9anor\n",
+ 'body really matches');
+ my $art = $n->article(1);
+ is(ref($art), 'ARRAY', 'got array for ARTICLE');
+ is_deeply($art, $n->article('<nntp@example.com>'), 'ARTICLE OK');
+ is($n->article(999), undef, 'non-existent num');
+ is($n->article('<non-existent@example>'), undef, 'non-existent mid');
+
{
syswrite($s, "OVER $mid\r\n");
- $buf = '';
- do {
- sysread($s, $buf, 4096, length($buf));
- } until ($buf =~ /^[^2]../ || $buf =~ /\r\n\.\r\n\z/);
+ $buf = read_til_dot($s);
my @r = split("\r\n", $buf);
like($r[0], qr/^224 /, 'got 224 response for OVER');
- is($r[1], "0\thihi\tMe <me\@example.com>\t" .
+ is($r[1], "0\tTesting for El\xc3\xa9anor\t" .
+ "El\xc3\xa9anor <me\@example.com>\t" .
"Thu, 01 Jan 1970 06:06:06 +0000\t" .
- "$mid\t\t202\t1", 'OVER by Message-ID works');
+ "$mid\t\t$len\t1", 'OVER by Message-ID works');
is($r[2], '.', 'correctly terminated response');
}
- ok(kill('TERM', $pid), 'killed nntpd');
- $pid = undef;
- waitpid(-1, 0);
+ is_deeply($n->xhdr(qw(Cc 1-)), { 1 => 'test-nntpd@example.com' },
+ 'XHDR Cc 1- works');
+ is_deeply($n->xhdr(qw(References 1-)), { 1 => '' },
+ 'XHDR References 1- works (empty string)');
+ is_deeply($n->xhdr(qw(list-id 1-)), {},
+ 'XHDR on invalid header returns empty');
+
+ my $mids = $n->newnews(0, '*');
+ is_deeply($mids, ['<nntp@example.com>'], 'NEWNEWS works');
+ {
+ my $t0 = time;
+ my $date = $n->date;
+ my $t1 = time;
+ ok($date >= $t0, 'valid date after start');
+ ok($date <= $t1, 'valid date before stop');
+ }
+
+ {
+ setsockopt($s, IPPROTO_TCP, TCP_NODELAY, 1);
+ syswrite($s, 'HDR List-id 1-');
+ select(undef, undef, undef, 0.15);
+ ok(kill('TERM', $pid), 'killed nntpd');
+ select(undef, undef, undef, 0.15);
+ syswrite($s, "\r\n");
+ $buf = '';
+ do {
+ sysread($s, $buf, 4096, length($buf));
+ } until ($buf =~ /\r\n\z/);
+ my @r = split("\r\n", $buf);
+ like($r[0], qr/^5\d\d /,
+ 'got 5xx response for unoptimized HDR');
+ is(scalar @r, 1, 'only one response line');
+ }
+
+ $n = $s = undef;
+ is($pid, waitpid($pid, 0), 'nntpd exited successfully');
+ my $eout = eval {
+ local $/;
+ open my $fh, '<', $err or die "open $err failed: $!";
+ <$fh>;
+ };
+ is($?, 0, 'no error in exited process');
+ unlike($eout, qr/wide/i, 'no Wide character warnings');
}
done_testing();
+sub read_til_dot {
+ my ($s) = @_;
+ my $buf = '';
+ do {
+ sysread($s, $buf, 4096, length($buf));
+ } until ($buf =~ /\r\n\.\r\n\z/);
+ $buf;
+}
+
1;