1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Vectorface\SnappyRouterTests\Handler; |
4
|
|
|
|
5
|
|
|
use \Exception; |
6
|
|
|
use \ReflectionClass; |
7
|
|
|
use PHPUnit\Framework\TestCase; |
8
|
|
|
use Vectorface\SnappyRouter\Config\Config; |
9
|
|
|
use Vectorface\SnappyRouter\Handler\JsonRpcHandler; |
10
|
|
|
|
11
|
|
|
/** |
12
|
|
|
* A test for the JsonRpcHandler class. |
13
|
|
|
* |
14
|
|
|
* @copyright Copyright (c) 2014, VectorFace, Inc. |
15
|
|
|
*/ |
16
|
|
|
class JsonRpcHandlerTest extends TestCase |
17
|
|
|
{ |
18
|
|
|
/** |
19
|
|
|
* Helper method to override the internals of php://input for test purposes. |
20
|
|
|
* @param JsonRpcHandler $handler The handler to override. |
21
|
|
|
* @param mixed $payload The payload to hand off for the request input. |
22
|
|
|
*/ |
23
|
|
|
public static function setRequestPayload(JsonRpcHandler $handler, $payload) |
24
|
|
|
{ |
25
|
|
|
$tmpfile = tempnam(sys_get_temp_dir(), __CLASS__); |
26
|
|
|
file_put_contents($tmpfile, is_string($payload) ? $payload : json_encode($payload)); |
27
|
|
|
|
28
|
|
|
$refCls = new ReflectionClass($handler); |
29
|
|
|
$prop = $refCls->getProperty('stdin'); |
30
|
|
|
$prop->setAccessible(true); |
31
|
|
|
$prop->setValue($handler, $tmpfile); |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* An overview of how to use the JsonRpcHandler class. |
36
|
|
|
* @test |
37
|
|
|
*/ |
38
|
|
|
public function synopsis() |
39
|
|
|
{ |
40
|
|
|
$options = array( |
41
|
|
|
Config::KEY_CONTROLLERS => array( |
42
|
|
|
'TestController' => 'Vectorface\\SnappyRouterTests\\Controller\\TestDummyController' |
43
|
|
|
) |
44
|
|
|
); |
45
|
|
|
$handler = new JsonRpcHandler($options); |
46
|
|
|
|
47
|
|
|
/* Ignores unconfigured services and anything other than POST */ |
48
|
|
|
$this->assertFalse($handler->isAppropriate('/anything', array(), array(), 'GET')); |
49
|
|
|
$this->assertFalse($handler->isAppropriate('/anything', array(), array(), 'POST')); |
50
|
|
|
|
51
|
|
|
/* A single JSON-RPC 1.0 request. */ |
52
|
|
|
$this->setRequestPayload($handler, array('method' => 'testAction', 'id' => '1')); |
53
|
|
|
$this->assertTrue($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
54
|
|
|
|
55
|
|
|
$result = json_decode($handler->performRoute()); |
56
|
|
|
$this->assertEquals("This is a test service.", $result->result); |
57
|
|
|
$this->assertEquals(1, $result->id); |
58
|
|
|
|
59
|
|
|
/* A batch of JSON-RPC 2.0 requests. */ |
60
|
|
|
$this->setRequestPayload($handler, array( |
61
|
|
|
array('jsonrpc' => '2.0', 'method' => 'testAction', 'id' => '1'), |
62
|
|
|
array('jsonrpc' => '2.0', 'method' => 'testAction', 'id' => '2') |
63
|
|
|
)); |
64
|
|
|
$this->assertTrue($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
65
|
|
|
$this->assertCount(2, $handler->getRequests()); |
66
|
|
|
|
67
|
|
|
$result = json_decode($handler->performRoute()); |
68
|
|
|
$this->assertCount(2, $result, "Expect 2 responses for 2 calls"); |
69
|
|
|
$this->assertEquals("2.0", $result[0]->jsonrpc); |
70
|
|
|
$this->assertEquals("This is a test service.", $result[0]->result); |
71
|
|
|
$this->assertEquals("1", $result[0]->id); |
72
|
|
|
$this->assertEquals("2.0", $result[1]->jsonrpc); |
73
|
|
|
$this->assertEquals("This is a test service.", $result[1]->result); |
74
|
|
|
$this->assertEquals("2", $result[1]->id); |
75
|
|
|
|
76
|
|
|
/* Handles notifications without replying. */ |
77
|
|
|
$this->setRequestPayload($handler, array( |
78
|
|
|
array('jsonrpc' => '2.0', 'method' => 'testAction', 'id' => '1'), |
79
|
|
|
array('jsonrpc' => '2.0', 'method' => 'testAction') // Notification |
80
|
|
|
)); |
81
|
|
|
$this->assertTrue($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
82
|
|
|
$result = json_decode($handler->performRoute()); |
83
|
|
|
$this->assertCount(1, $result, "Expect 1 response for 1 call and 1 notification"); |
84
|
|
|
$this->assertEquals("1", $result[0]->id); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* Tests various edge cases of the JsonRpcHandler::isAppropriate method. |
89
|
|
|
*/ |
90
|
|
|
public function testIsAppropriate() |
91
|
|
|
{ |
92
|
|
|
/* Without a base path, only the last path element is used to map the controller/service */ |
93
|
|
|
$options = array( |
94
|
|
|
Config::KEY_CONTROLLERS => array( |
95
|
|
|
'TestController' => 'Vectorface\\SnappyRouterTests\\Controller\\TestDummyController' |
96
|
|
|
) |
97
|
|
|
); |
98
|
|
|
$handler = new JsonRpcHandler($options); |
99
|
|
|
|
100
|
|
|
/* Ignores non-JSON POST'ed data */ |
101
|
|
|
$this->setRequestPayload($handler, "<?xml version=\"1.0\" encoding=\"UTF-8\"?><document></document>"); |
102
|
|
|
$this->assertFalse($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
103
|
|
|
|
104
|
|
|
/* With a base path, a longer controller key can be used */ |
105
|
|
|
$options = array( |
106
|
|
|
JsonRpcHandler::KEY_BASE_PATH => 'x/y/z', |
107
|
|
|
Config::KEY_CONTROLLERS => array( |
108
|
|
|
'Test/TestController' => 'Vectorface\\SnappyRouterTests\\Controller\\TestDummyController' |
109
|
|
|
) |
110
|
|
|
); |
111
|
|
|
$handler = new JsonRpcHandler($options); |
112
|
|
|
$this->setRequestPayload($handler, array()); |
113
|
|
|
$this->assertTrue($handler->isAppropriate("/x////y//z/Test/TestController.php", array(), array(), 'POST')); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* Tests the edge cases of the JsonRpcHandler::performRoute method. |
118
|
|
|
*/ |
119
|
|
|
public function testPerformRoute() |
120
|
|
|
{ |
121
|
|
|
$options = array( |
122
|
|
|
Config::KEY_CONTROLLERS => array( |
123
|
|
|
'TestController' => 'Vectorface\\SnappyRouterTests\\Controller\\TestDummyController' |
124
|
|
|
) |
125
|
|
|
); |
126
|
|
|
$handler = new JsonRpcHandler($options); |
127
|
|
|
|
128
|
|
|
/* Blows up safely for invalid requests. */ |
129
|
|
|
$this->setRequestPayload($handler, array('id' => '1')); |
130
|
|
|
$this->assertTrue($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
131
|
|
|
$result = json_decode($handler->performRoute()); |
132
|
|
|
$this->assertFalse(isset($result->result)); |
133
|
|
|
$this->assertEquals(-32600, $result->error->code); |
134
|
|
|
|
135
|
|
|
/* Blows up safely for internal errors */ |
136
|
|
|
$this->setRequestPayload($handler, array('method' => 'genericExceptionAction', 'id' => '1')); |
137
|
|
|
$this->assertTrue($handler->isAppropriate('/x/y/z/TestController', array(), array(), 'POST')); |
138
|
|
|
$result = json_decode($handler->performRoute()); |
139
|
|
|
$this->assertFalse(isset($result->result)); |
140
|
|
|
$this->assertEquals('A generic exception.', $result->error->message); |
141
|
|
|
$this->assertTrue(-32000 >= $result->error->code); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
/** |
145
|
|
|
* Tests the method JsonRpcHandler::handleException. |
146
|
|
|
*/ |
147
|
|
|
public function testHandleException() |
148
|
|
|
{ |
149
|
|
|
$handler = new JsonRpcHandler(array()); |
150
|
|
|
/* Any internal error should be wrapped in a JSON-RPC exception. */ |
151
|
|
|
$internal = $handler->handleException(new Exception("foo", 123)); |
152
|
|
|
$this->assertTrue($internal->error->code <= -32000); |
153
|
|
|
|
154
|
|
|
/* Any JSON-RPC exception (code <= 32000) can pass through */ |
155
|
|
|
$passthrough = $handler->handleException(new Exception("passthrough", -32001)); |
156
|
|
|
$this->assertEquals(-32001, $passthrough->error->code); |
157
|
|
|
$this->assertEquals("passthrough", $passthrough->error->message); |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* Tests that a request to a service that doesn't exist returns a 404 |
162
|
|
|
* response. |
163
|
|
|
* @expectedException Vectorface\SnappyRouter\Exception\ResourceNotFoundException |
164
|
|
|
* @expectedExceptionMessage No such service: nonexistent |
165
|
|
|
*/ |
166
|
|
|
public function testServiceNotFound() |
167
|
|
|
{ |
168
|
|
|
$handler = new JsonRpcHandler(array()); |
169
|
|
|
$this->setRequestPayload($handler, array('method' => 'someMethod', 'id' => '1')); |
170
|
|
|
$this->assertTrue($handler->isAppropriate('/nonexistent', array(), array(), 'POST')); |
171
|
|
|
$handler->performRoute(); |
172
|
|
|
} |
173
|
|
|
} |
174
|
|
|
|