Skip to content

Commit 1030e75

Browse files
committed
Add new FallbackExecutor
1 parent 1657d63 commit 1030e75

File tree

2 files changed

+185
-0
lines changed

2 files changed

+185
-0
lines changed

src/Query/FallbackExecutor.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace React\Dns\Query;
4+
5+
final class FallbackExecutor implements ExecutorInterface
6+
{
7+
private $executor;
8+
private $fallback;
9+
10+
public function __construct(ExecutorInterface $executor, ExecutorInterface $fallback)
11+
{
12+
$this->executor = $executor;
13+
$this->fallback = $fallback;
14+
}
15+
16+
public function query(Query $query)
17+
{
18+
$fallback = $this->fallback;
19+
return $this->executor->query($query)->then(null, function (\Exception $e1) use ($query, $fallback) {
20+
return $fallback->query($query)->then(null, function (\Exception $e2) use ($e1) {
21+
$append = $e2->getMessage();
22+
if (($pos = strpos($append, ':')) !== false) {
23+
$append = substr($append, $pos + 2);
24+
}
25+
26+
throw new \RuntimeException($e1->getMessage() . '. ' . $append);
27+
});
28+
});
29+
}
30+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
<?php
2+
3+
namespace React\Tests\Dns\Query;
4+
5+
use React\Dns\Model\Message;
6+
use React\Dns\Query\FallbackExecutor;
7+
use React\Dns\Query\Query;
8+
use React\Promise\Promise;
9+
use React\Tests\Dns\TestCase;
10+
11+
class FallbackExecutorTest extends TestCase
12+
{
13+
public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorIsStillPending()
14+
{
15+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
16+
17+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
18+
$primary->expects($this->once())->method('query')->with($query)->willReturn(new Promise(function () { }));
19+
20+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
21+
22+
$executor = new FallbackExecutor($primary, $secondary);
23+
24+
$promise = $executor->query($query);
25+
26+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
27+
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
28+
}
29+
30+
public function testQueryWillResolveWithMessageWhenPrimaryExecutorResolvesWithMessage()
31+
{
32+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
33+
34+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
35+
$primary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\resolve(new Message()));
36+
37+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
38+
39+
$executor = new FallbackExecutor($primary, $secondary);
40+
41+
$promise = $executor->query($query);
42+
43+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
44+
$promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\Dns\Model\Message')), $this->expectCallableNever());
45+
}
46+
47+
public function testQueryWillReturnPendingPromiseWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorIsStillPending()
48+
{
49+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
50+
51+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
52+
$primary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException()));
53+
54+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
55+
$secondary->expects($this->once())->method('query')->with($query)->willReturn(new Promise(function () { }));
56+
57+
$executor = new FallbackExecutor($primary, $secondary);
58+
59+
$promise = $executor->query($query);
60+
61+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
62+
$promise->then($this->expectCallableNever(), $this->expectCallableNever());
63+
}
64+
65+
public function testQueryWillResolveWithMessageWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorResolvesWithMessage()
66+
{
67+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
68+
69+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
70+
$primary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException()));
71+
72+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
73+
$secondary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\resolve(new Message()));
74+
75+
$executor = new FallbackExecutor($primary, $secondary);
76+
77+
$promise = $executor->query($query);
78+
79+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
80+
$promise->then($this->expectCallableOnceWith($this->isInstanceOf('React\Dns\Model\Message')), $this->expectCallableNever());
81+
}
82+
83+
public function testQueryWillRejectWithExceptionMessagesConcatenatedAfterColonWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithColon()
84+
{
85+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
86+
87+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
88+
$primary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException('DNS query for reactphp.org (A) failed: Unable to connect to DNS server A')));
89+
90+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
91+
$secondary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException('DNS query for reactphp.org (A) failed: Unable to connect to DNS server B')));
92+
93+
$executor = new FallbackExecutor($primary, $secondary);
94+
95+
$promise = $executor->query($query);
96+
97+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
98+
$promise->then($this->expectCallableNever(), $this->expectCallableOnce($this->isInstanceOf('Exception')));
99+
100+
$exception = null;
101+
$promise->then(null, function ($reason) use (&$exception) {
102+
$exception = $reason;
103+
});
104+
105+
$this->assertInstanceOf('RuntimeException', $exception);
106+
$this->assertEquals('DNS query for reactphp.org (A) failed: Unable to connect to DNS server A. Unable to connect to DNS server B', $exception->getMessage());
107+
}
108+
109+
public function testQueryWillRejectWithExceptionMessagesConcatenatedInFullWhenPrimaryExecutorRejectsPromiseAndSecondaryExecutorRejectsPromiseWithMessageWithNoColon()
110+
{
111+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
112+
113+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
114+
$primary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException('Reason A')));
115+
116+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
117+
$secondary->expects($this->once())->method('query')->with($query)->willReturn(\React\Promise\reject(new \RuntimeException('Reason B')));
118+
119+
$executor = new FallbackExecutor($primary, $secondary);
120+
121+
$promise = $executor->query($query);
122+
123+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
124+
$promise->then($this->expectCallableNever(), $this->expectCallableOnce($this->isInstanceOf('Exception')));
125+
126+
$exception = null;
127+
$promise->then(null, function ($reason) use (&$exception) {
128+
$exception = $reason;
129+
});
130+
131+
$this->assertInstanceOf('RuntimeException', $exception);
132+
$this->assertEquals('Reason A. Reason B', $exception->getMessage());
133+
}
134+
135+
public function testCancelQueryWillReturnRejectedPromiseWithoutCallingSecondaryExecutorWhenPrimaryExecutorIsStillPending()
136+
{
137+
$this->markTestIncomplete();
138+
139+
$query = new Query('reactphp.org', Message::TYPE_A, Message::CLASS_IN);
140+
141+
$primary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
142+
$primary->expects($this->once())->method('query')->with($query)->willReturn(new Promise(function () { }, function () { throw new \RuntimeException(); }));
143+
144+
$secondary = $this->getMockBuilder('React\Dns\Query\ExecutorInterface')->getMock();
145+
$secondary->expects($this->never())->method('query');
146+
147+
$executor = new FallbackExecutor($primary, $secondary);
148+
149+
$promise = $executor->query($query);
150+
$promise->cancel();
151+
152+
$this->assertInstanceOf('React\Promise\PromiseInterface', $promise);
153+
$promise->then($this->expectCallableNever(), $this->expectCallableOnce());
154+
}
155+
}

0 commit comments

Comments
 (0)