1use Mojo::Base -strict;
2
3use Test::More;
4
5plan skip_all => 'set TEST_ONLINE to enable this test'
6  unless $ENV{TEST_ONLINE};
7
8use Mango;
9use Mojo::IOLoop;
10use Mojo::IOLoop::Server;
11
12# Defaults
13my $mango = Mango->new;
14is_deeply $mango->hosts, [['localhost']], 'right hosts';
15is $mango->default_db, 'admin', 'right default database';
16is $mango->inactivity_timeout, 0,    'right timeout value';
17is $mango->j,                  0,    'right j value';
18is $mango->w,                  1,    'right w value';
19is $mango->wtimeout,           1000, 'right wtimeout value';
20is $mango->backlog,            0,    'no operations waiting';
21
22# Simple connection string
23$mango = Mango->new('mongodb://127.0.0.1:3000');
24is_deeply $mango->hosts, [['127.0.0.1', 3000]], 'right hosts';
25is $mango->default_db, 'admin', 'right default database';
26is $mango->j,        0,    'right j value';
27is $mango->w,        1,    'right w value';
28is $mango->wtimeout, 1000, 'right wtimeout value';
29
30# Complex connection string
31$mango = Mango->new(
32  'mongodb://x1:y2@foo.bar:5000,baz:3000/test?journal=1&w=2&wtimeoutMS=2000');
33is_deeply $mango->hosts, [['foo.bar', 5000], ['baz', 3000]], 'right hosts';
34is $mango->default_db, 'test', 'right default database';
35is $mango->j,        1,    'right j value';
36is $mango->w,        2,    'right w value';
37is $mango->wtimeout, 2000, 'right wtimeout value';
38is $mango->db->name, 'test', 'right database name';
39
40# Invalid connection string
41eval { Mango->new('http://localhost:3000/test') };
42like $@, qr/Invalid MongoDB connection string/, 'right error';
43
44# No port
45$mango = Mango->new->from_string('mongodb://127.0.0.1,127.0.0.1:5000');
46is_deeply $mango->hosts, [['127.0.0.1'], ['127.0.0.1', 5000]], 'right hosts';
47
48# Connection error
49my $port = Mojo::IOLoop::Server->generate_port;
50eval { Mango->new("mongodb://127.0.0.1:$port/test")->db->command('getnonce') };
51ok $@, 'has error';
52
53# Clean up before start
54$mango = Mango->new($ENV{TEST_ONLINE});
55my $collection = $mango->db->collection('connection_test');
56$collection->drop if $collection->options;
57
58# Blocking CRUD
59my $oid = $collection->insert({foo => 'bar'});
60is $mango->backlog, 0, 'no operations waiting';
61isa_ok $oid, 'Mango::BSON::ObjectID', 'right class';
62my $doc = $collection->find_one({foo => 'bar'});
63is_deeply $doc, {_id => $oid, foo => 'bar'}, 'right document';
64$doc->{foo} = 'yada';
65is $collection->update({foo => 'bar'}, $doc)->{n}, 1, 'one document updated';
66$doc = $collection->find_one($oid);
67is_deeply $doc, {_id => $oid, foo => 'yada'}, 'right document';
68is $collection->remove->{n}, 1, 'one document removed';
69
70# Non-blocking CRUD
71my ($fail, $backlog, $created, $updated, $found, $removed);
72my $delay = Mojo::IOLoop->delay(
73  sub {
74    my $delay = shift;
75    $collection->insert({foo => 'bar'} => $delay->begin);
76    $backlog = $collection->db->mango->backlog;
77  },
78  sub {
79    my ($delay, $err, $oid) = @_;
80    return $delay->pass($err) if $err;
81    $created = $oid;
82    $collection->find_one({foo => 'bar'} => $delay->begin);
83  },
84  sub {
85    my ($delay, $err, $doc) = @_;
86    return $delay->pass($err) if $err;
87    $doc->{foo} = 'yada';
88    $collection->update(({foo => 'bar'}, $doc) => $delay->begin);
89  },
90  sub {
91    my ($delay, $err, $doc) = @_;
92    return $delay->pass($err) if $err;
93    $updated = $doc;
94    $collection->find_one($created => $delay->begin);
95  },
96  sub {
97    my ($delay, $err, $doc) = @_;
98    return $delay->pass($err) if $err;
99    $found = $doc;
100    $collection->remove($delay->begin);
101  },
102  sub {
103    my ($delay, $err, $doc) = @_;
104    $fail    = $err;
105    $removed = $doc;
106  }
107);
108$delay->wait;
109ok !$fail, 'no error';
110is $backlog, 1, 'one operation waiting';
111isa_ok $created, 'Mango::BSON::ObjectID', 'right class';
112is $updated->{n}, 1, 'one document updated';
113is_deeply $found, {_id => $created, foo => 'yada'}, 'right document';
114is $removed->{n}, 1, 'one document removed';
115
116# Error in callback
117Mojo::IOLoop->singleton->reactor->unsubscribe('error');
118$fail = undef;
119Mojo::IOLoop->singleton->reactor->once(
120  error => sub { $fail .= pop; Mojo::IOLoop->stop });
121$collection->insert({foo => 'bar'} => sub { die 'Oops!' });
122Mojo::IOLoop->start;
123like $fail, qr/Oops!/, 'right error';
124is $collection->remove->{n}, 1, 'one document removed';
125
126# Fork safety
127$mango      = Mango->new($ENV{TEST_ONLINE});
128$collection = $mango->db->collection('connection_test');
129my ($connections, $current);
130$mango->on(
131  connection => sub {
132    my ($mango, $id) = @_;
133    $connections++;
134    $current = $id;
135  }
136);
137is $collection->find->count, 0, 'no documents';
138is $connections, 1, 'one connection';
139ok $mango->ioloop->stream($current), 'connection exists';
140my $last = $current;
141is $collection->find->count, 0, 'no documents';
142is $connections, 1, 'one connection';
143ok $mango->ioloop->stream($current), 'connection exists';
144is $last, $current, 'same connection';
145{
146  local $$ = -23;
147  is $collection->find->count, 0, 'no documents';
148  is $connections, 2, 'two connections';
149  ok $mango->ioloop->stream($current), 'connection exists';
150  isnt $last, $current, 'different connections';
151  $last = $current;
152  is $collection->find->count, 0, 'no documents';
153  is $connections, 2, 'two connections';
154  ok $mango->ioloop->stream($current), 'connection exists';
155  is $last, $current, 'same connection';
156}
157
158# Mixed concurrent operations
159$collection->insert({test => $_}) for 1 .. 3;
160is $mango->backlog, 0, 'no operations waiting';
161my @results;
162$delay = Mojo::IOLoop->delay(sub { shift; @results = @_ });
163$collection->find_one(({test => $_}, {_id => 0}) => $delay->begin) for 1 .. 3;
164is $mango->backlog, 3, 'three operations waiting';
165is $collection->find_one({test => 1})->{test}, 1, 'right result';
166$delay->wait;
167is $mango->backlog, 0, 'no operations waiting';
168ok !$results[0], 'no error';
169is_deeply $results[1], {test => 1}, 'right result';
170ok !$results[2], 'no error';
171is_deeply $results[3], {test => 2}, 'right result';
172ok !$results[4], 'no error';
173is_deeply $results[5], {test => 3}, 'right result';
174is $collection->remove->{n}, 3, 'three documents removed';
175
176# Fallback server
177$mango = Mango->new($ENV{TEST_ONLINE});
178$port  = Mojo::IOLoop::Server->generate_port;
179unshift @{$mango->hosts}, ['127.0.0.1', $port];
180ok $mango->db->command('getnonce')->{nonce}, 'command was successful';
181is_deeply $mango->hosts->[0], ['127.0.0.1', $port], 'right server';
182ok scalar @{$mango->hosts} > 1, 'more than one server';
183
184done_testing();
185