aboutsummaryrefslogtreecommitdiffhomepage
path: root/tests/api
diff options
context:
space:
mode:
Diffstat (limited to 'tests/api')
-rw-r--r--tests/api/ApiMiddlewareTest.php208
-rw-r--r--tests/api/ApiUtilsTest.php352
-rw-r--r--tests/api/controllers/DeleteLinkTest.php126
-rw-r--r--tests/api/controllers/GetLinkIdTest.php132
-rw-r--r--tests/api/controllers/GetLinksTest.php472
-rw-r--r--tests/api/controllers/HistoryTest.php216
-rw-r--r--tests/api/controllers/InfoTest.php115
-rw-r--r--tests/api/controllers/PostLinkTest.php216
-rw-r--r--tests/api/controllers/PutLinkTest.php222
9 files changed, 2059 insertions, 0 deletions
diff --git a/tests/api/ApiMiddlewareTest.php b/tests/api/ApiMiddlewareTest.php
new file mode 100644
index 00000000..23a56b1c
--- /dev/null
+++ b/tests/api/ApiMiddlewareTest.php
@@ -0,0 +1,208 @@
1<?php
2namespace Shaarli\Api;
3
4use Shaarli\Config\ConfigManager;
5
6use Slim\Container;
7use Slim\Http\Environment;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class ApiMiddlewareTest
13 *
14 * Test the REST API Slim Middleware.
15 *
16 * Note that we can't test a valid use case here, because the middleware
17 * needs to call a valid controller/action during its execution.
18 *
19 * @package Api
20 */
21class ApiMiddlewareTest extends \PHPUnit_Framework_TestCase
22{
23 /**
24 * @var string datastore to test write operations
25 */
26 protected static $testDatastore = 'sandbox/datastore.php';
27
28 /**
29 * @var \ConfigManager instance
30 */
31 protected $conf;
32
33 /**
34 * @var \ReferenceLinkDB instance.
35 */
36 protected $refDB = null;
37
38 /**
39 * @var Container instance.
40 */
41 protected $container;
42
43 /**
44 * Before every test, instantiate a new Api with its config, plugins and links.
45 */
46 public function setUp()
47 {
48 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
49 $this->conf->set('api.secret', 'NapoleonWasALizard');
50
51 $this->refDB = new \ReferenceLinkDB();
52 $this->refDB->write(self::$testDatastore);
53
54 $this->container = new Container();
55 $this->container['conf'] = $this->conf;
56 }
57
58 /**
59 * After every test, remove the test datastore.
60 */
61 public function tearDown()
62 {
63 @unlink(self::$testDatastore);
64 }
65
66 /**
67 * Invoke the middleware with the API disabled:
68 * should return a 401 error Unauthorized.
69 */
70 public function testInvokeMiddlewareApiDisabled()
71 {
72 $this->conf->set('api.enabled', false);
73 $mw = new ApiMiddleware($this->container);
74 $env = Environment::mock([
75 'REQUEST_METHOD' => 'GET',
76 'REQUEST_URI' => '/echo',
77 ]);
78 $request = Request::createFromEnvironment($env);
79 $response = new Response();
80 /** @var Response $response */
81 $response = $mw($request, $response, null);
82
83 $this->assertEquals(401, $response->getStatusCode());
84 $body = json_decode((string) $response->getBody());
85 $this->assertEquals('Not authorized', $body);
86 }
87
88 /**
89 * Invoke the middleware with the API disabled in debug mode:
90 * should return a 401 error Unauthorized - with a specific message and a stacktrace.
91 */
92 public function testInvokeMiddlewareApiDisabledDebug()
93 {
94 $this->conf->set('api.enabled', false);
95 $this->conf->set('dev.debug', true);
96 $mw = new ApiMiddleware($this->container);
97 $env = Environment::mock([
98 'REQUEST_METHOD' => 'GET',
99 'REQUEST_URI' => '/echo',
100 ]);
101 $request = Request::createFromEnvironment($env);
102 $response = new Response();
103 /** @var Response $response */
104 $response = $mw($request, $response, null);
105
106 $this->assertEquals(401, $response->getStatusCode());
107 $body = json_decode((string) $response->getBody());
108 $this->assertEquals('Not authorized: API is disabled', $body->message);
109 $this->assertContains('ApiAuthorizationException', $body->stacktrace);
110 }
111
112 /**
113 * Invoke the middleware without a token (debug):
114 * should return a 401 error Unauthorized - with a specific message and a stacktrace.
115 */
116 public function testInvokeMiddlewareNoTokenProvidedDebug()
117 {
118 $this->conf->set('dev.debug', true);
119 $mw = new ApiMiddleware($this->container);
120 $env = Environment::mock([
121 'REQUEST_METHOD' => 'GET',
122 'REQUEST_URI' => '/echo',
123 ]);
124 $request = Request::createFromEnvironment($env);
125 $response = new Response();
126 /** @var Response $response */
127 $response = $mw($request, $response, null);
128
129 $this->assertEquals(401, $response->getStatusCode());
130 $body = json_decode((string) $response->getBody());
131 $this->assertEquals('Not authorized: JWT token not provided', $body->message);
132 $this->assertContains('ApiAuthorizationException', $body->stacktrace);
133 }
134
135 /**
136 * Invoke the middleware without a secret set in settings (debug):
137 * should return a 401 error Unauthorized - with a specific message and a stacktrace.
138 */
139 public function testInvokeMiddlewareNoSecretSetDebug()
140 {
141 $this->conf->set('dev.debug', true);
142 $this->conf->set('api.secret', '');
143 $mw = new ApiMiddleware($this->container);
144 $env = Environment::mock([
145 'REQUEST_METHOD' => 'GET',
146 'REQUEST_URI' => '/echo',
147 'HTTP_AUTHORIZATION'=> 'Bearer jwt',
148 ]);
149 $request = Request::createFromEnvironment($env);
150 $response = new Response();
151 /** @var Response $response */
152 $response = $mw($request, $response, null);
153
154 $this->assertEquals(401, $response->getStatusCode());
155 $body = json_decode((string) $response->getBody());
156 $this->assertEquals('Not authorized: Token secret must be set in Shaarli\'s administration', $body->message);
157 $this->assertContains('ApiAuthorizationException', $body->stacktrace);
158 }
159
160 /**
161 * Invoke the middleware with an invalid JWT token header
162 */
163 public function testInvalidJwtAuthHeaderDebug()
164 {
165 $this->conf->set('dev.debug', true);
166 $mw = new ApiMiddleware($this->container);
167 $env = Environment::mock([
168 'REQUEST_METHOD' => 'GET',
169 'REQUEST_URI' => '/echo',
170 'HTTP_AUTHORIZATION'=> 'PolarBearer jwt',
171 ]);
172 $request = Request::createFromEnvironment($env);
173 $response = new Response();
174 /** @var Response $response */
175 $response = $mw($request, $response, null);
176
177 $this->assertEquals(401, $response->getStatusCode());
178 $body = json_decode((string) $response->getBody());
179 $this->assertEquals('Not authorized: Invalid JWT header', $body->message);
180 $this->assertContains('ApiAuthorizationException', $body->stacktrace);
181 }
182
183 /**
184 * Invoke the middleware with an invalid JWT token (debug):
185 * should return a 401 error Unauthorized - with a specific message and a stacktrace.
186 *
187 * Note: specific JWT errors tests are handled in ApiUtilsTest.
188 */
189 public function testInvokeMiddlewareInvalidJwtDebug()
190 {
191 $this->conf->set('dev.debug', true);
192 $mw = new ApiMiddleware($this->container);
193 $env = Environment::mock([
194 'REQUEST_METHOD' => 'GET',
195 'REQUEST_URI' => '/echo',
196 'HTTP_AUTHORIZATION'=> 'Bearer jwt',
197 ]);
198 $request = Request::createFromEnvironment($env);
199 $response = new Response();
200 /** @var Response $response */
201 $response = $mw($request, $response, null);
202
203 $this->assertEquals(401, $response->getStatusCode());
204 $body = json_decode((string) $response->getBody());
205 $this->assertEquals('Not authorized: Malformed JWT token', $body->message);
206 $this->assertContains('ApiAuthorizationException', $body->stacktrace);
207 }
208}
diff --git a/tests/api/ApiUtilsTest.php b/tests/api/ApiUtilsTest.php
new file mode 100644
index 00000000..62baf4c5
--- /dev/null
+++ b/tests/api/ApiUtilsTest.php
@@ -0,0 +1,352 @@
1<?php
2
3namespace Shaarli\Api;
4
5use Shaarli\Base64Url;
6
7
8/**
9 * Class ApiUtilsTest
10 */
11class ApiUtilsTest extends \PHPUnit_Framework_TestCase
12{
13 /**
14 * Force the timezone for ISO datetimes.
15 */
16 public static function setUpBeforeClass()
17 {
18 date_default_timezone_set('UTC');
19 }
20
21 /**
22 * Generate a valid JWT token.
23 *
24 * @param string $secret API secret used to generate the signature.
25 *
26 * @return string Generated token.
27 */
28 public static function generateValidJwtToken($secret)
29 {
30 $header = Base64Url::encode('{
31 "typ": "JWT",
32 "alg": "HS512"
33 }');
34 $payload = Base64Url::encode('{
35 "iat": '. time() .'
36 }');
37 $signature = Base64Url::encode(hash_hmac('sha512', $header .'.'. $payload , $secret, true));
38 return $header .'.'. $payload .'.'. $signature;
39 }
40
41 /**
42 * Generate a JWT token from given header and payload.
43 *
44 * @param string $header Header in JSON format.
45 * @param string $payload Payload in JSON format.
46 * @param string $secret API secret used to hash the signature.
47 *
48 * @return string JWT token.
49 */
50 public static function generateCustomJwtToken($header, $payload, $secret)
51 {
52 $header = Base64Url::encode($header);
53 $payload = Base64Url::encode($payload);
54 $signature = Base64Url::encode(hash_hmac('sha512', $header . '.' . $payload, $secret, true));
55 return $header . '.' . $payload . '.' . $signature;
56 }
57
58 /**
59 * Test validateJwtToken() with a valid JWT token.
60 */
61 public function testValidateJwtTokenValid()
62 {
63 $secret = 'WarIsPeace';
64 ApiUtils::validateJwtToken(self::generateValidJwtToken($secret), $secret);
65 }
66
67 /**
68 * Test validateJwtToken() with a malformed JWT token.
69 *
70 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
71 * @expectedExceptionMessage Malformed JWT token
72 */
73 public function testValidateJwtTokenMalformed()
74 {
75 $token = 'ABC.DEF';
76 ApiUtils::validateJwtToken($token, 'foo');
77 }
78
79 /**
80 * Test validateJwtToken() with an empty JWT token.
81 *
82 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
83 * @expectedExceptionMessage Malformed JWT token
84 */
85 public function testValidateJwtTokenMalformedEmpty()
86 {
87 $token = false;
88 ApiUtils::validateJwtToken($token, 'foo');
89 }
90
91 /**
92 * Test validateJwtToken() with a JWT token without header.
93 *
94 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
95 * @expectedExceptionMessage Malformed JWT token
96 */
97 public function testValidateJwtTokenMalformedEmptyHeader()
98 {
99 $token = '.payload.signature';
100 ApiUtils::validateJwtToken($token, 'foo');
101 }
102
103 /**
104 * Test validateJwtToken() with a JWT token without payload
105 *
106 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
107 * @expectedExceptionMessage Malformed JWT token
108 */
109 public function testValidateJwtTokenMalformedEmptyPayload()
110 {
111 $token = 'header..signature';
112 ApiUtils::validateJwtToken($token, 'foo');
113 }
114
115 /**
116 * Test validateJwtToken() with a JWT token with an empty signature.
117 *
118 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
119 * @expectedExceptionMessage Invalid JWT signature
120 */
121 public function testValidateJwtTokenInvalidSignatureEmpty()
122 {
123 $token = 'header.payload.';
124 ApiUtils::validateJwtToken($token, 'foo');
125 }
126
127 /**
128 * Test validateJwtToken() with a JWT token with an invalid signature.
129 *
130 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
131 * @expectedExceptionMessage Invalid JWT signature
132 */
133 public function testValidateJwtTokenInvalidSignature()
134 {
135 $token = 'header.payload.nope';
136 ApiUtils::validateJwtToken($token, 'foo');
137 }
138
139 /**
140 * Test validateJwtToken() with a JWT token with a signature generated with the wrong API secret.
141 *
142 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
143 * @expectedExceptionMessage Invalid JWT signature
144 */
145 public function testValidateJwtTokenInvalidSignatureSecret()
146 {
147 ApiUtils::validateJwtToken(self::generateValidJwtToken('foo'), 'bar');
148 }
149
150 /**
151 * Test validateJwtToken() with a JWT token with a an invalid header (not JSON).
152 *
153 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
154 * @expectedExceptionMessage Invalid JWT header
155 */
156 public function testValidateJwtTokenInvalidHeader()
157 {
158 $token = $this->generateCustomJwtToken('notJSON', '{"JSON":1}', 'secret');
159 ApiUtils::validateJwtToken($token, 'secret');
160 }
161
162 /**
163 * Test validateJwtToken() with a JWT token with a an invalid payload (not JSON).
164 *
165 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
166 * @expectedExceptionMessage Invalid JWT payload
167 */
168 public function testValidateJwtTokenInvalidPayload()
169 {
170 $token = $this->generateCustomJwtToken('{"JSON":1}', 'notJSON', 'secret');
171 ApiUtils::validateJwtToken($token, 'secret');
172 }
173
174 /**
175 * Test validateJwtToken() with a JWT token without issued time.
176 *
177 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
178 * @expectedExceptionMessage Invalid JWT issued time
179 */
180 public function testValidateJwtTokenInvalidTimeEmpty()
181 {
182 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"JSON":1}', 'secret');
183 ApiUtils::validateJwtToken($token, 'secret');
184 }
185
186 /**
187 * Test validateJwtToken() with an expired JWT token.
188 *
189 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
190 * @expectedExceptionMessage Invalid JWT issued time
191 */
192 public function testValidateJwtTokenInvalidTimeExpired()
193 {
194 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() - 600) . '}', 'secret');
195 ApiUtils::validateJwtToken($token, 'secret');
196 }
197
198 /**
199 * Test validateJwtToken() with a JWT token issued in the future.
200 *
201 * @expectedException \Shaarli\Api\Exceptions\ApiAuthorizationException
202 * @expectedExceptionMessage Invalid JWT issued time
203 */
204 public function testValidateJwtTokenInvalidTimeFuture()
205 {
206 $token = $this->generateCustomJwtToken('{"JSON":1}', '{"iat":' . (time() + 60) . '}', 'secret');
207 ApiUtils::validateJwtToken($token, 'secret');
208 }
209
210 /**
211 * Test formatLink() with a link using all useful fields.
212 */
213 public function testFormatLinkComplete()
214 {
215 $indexUrl = 'https://domain.tld/sub/';
216 $link = [
217 'id' => 12,
218 'url' => 'http://lol.lol',
219 'shorturl' => 'abc',
220 'title' => 'Important Title',
221 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
222 'tags' => 'blip .blop ',
223 'private' => '1',
224 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
225 'updated' => \DateTime::createFromFormat('Ymd_His', '20170107_160612'),
226 ];
227
228 $expected = [
229 'id' => 12,
230 'url' => 'http://lol.lol',
231 'shorturl' => 'abc',
232 'title' => 'Important Title',
233 'description' => 'It is very lol<tag>' . PHP_EOL . 'new line',
234 'tags' => ['blip', '.blop'],
235 'private' => true,
236 'created' => '2017-01-07T16:01:02+00:00',
237 'updated' => '2017-01-07T16:06:12+00:00',
238 ];
239
240 $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
241 }
242
243 /**
244 * Test formatLink() with only minimal fields filled, and internal link.
245 */
246 public function testFormatLinkMinimalNote()
247 {
248 $indexUrl = 'https://domain.tld/sub/';
249 $link = [
250 'id' => 12,
251 'url' => '?abc',
252 'shorturl' => 'abc',
253 'title' => 'Note',
254 'description' => '',
255 'tags' => '',
256 'private' => '',
257 'created' => \DateTime::createFromFormat('Ymd_His', '20170107_160102'),
258 ];
259
260 $expected = [
261 'id' => 12,
262 'url' => 'https://domain.tld/sub/?abc',
263 'shorturl' => 'abc',
264 'title' => 'Note',
265 'description' => '',
266 'tags' => [],
267 'private' => false,
268 'created' => '2017-01-07T16:01:02+00:00',
269 'updated' => '',
270 ];
271
272 $this->assertEquals($expected, ApiUtils::formatLink($link, $indexUrl));
273 }
274
275 /**
276 * Test updateLink with valid data, and also unnecessary fields.
277 */
278 public function testUpdateLink()
279 {
280 $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102');
281 $old = [
282 'id' => 12,
283 'url' => '?abc',
284 'shorturl' => 'abc',
285 'title' => 'Note',
286 'description' => '',
287 'tags' => '',
288 'private' => '',
289 'created' => $created,
290 ];
291
292 $new = [
293 'id' => 13,
294 'shorturl' => 'nope',
295 'url' => 'http://somewhere.else',
296 'title' => 'Le Cid',
297 'description' => 'Percé jusques au fond du cœur [...]',
298 'tags' => 'corneille rodrigue',
299 'private' => true,
300 'created' => 'creation',
301 'updated' => 'updation',
302 ];
303
304 $result = ApiUtils::updateLink($old, $new);
305 $this->assertEquals(12, $result['id']);
306 $this->assertEquals('http://somewhere.else', $result['url']);
307 $this->assertEquals('abc', $result['shorturl']);
308 $this->assertEquals('Le Cid', $result['title']);
309 $this->assertEquals('Percé jusques au fond du cœur [...]', $result['description']);
310 $this->assertEquals('corneille rodrigue', $result['tags']);
311 $this->assertEquals(true, $result['private']);
312 $this->assertEquals($created, $result['created']);
313 $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']);
314 }
315
316 /**
317 * Test updateLink with minimal data.
318 */
319 public function testUpdateLinkMinimal()
320 {
321 $created = \DateTime::createFromFormat('Ymd_His', '20170107_160102');
322 $old = [
323 'id' => 12,
324 'url' => '?abc',
325 'shorturl' => 'abc',
326 'title' => 'Note',
327 'description' => 'Interesting description!',
328 'tags' => 'doggo',
329 'private' => true,
330 'created' => $created,
331 ];
332
333 $new = [
334 'url' => '',
335 'title' => '',
336 'description' => '',
337 'tags' => '',
338 'private' => false,
339 ];
340
341 $result = ApiUtils::updateLink($old, $new);
342 $this->assertEquals(12, $result['id']);
343 $this->assertEquals('?abc', $result['url']);
344 $this->assertEquals('abc', $result['shorturl']);
345 $this->assertEquals('?abc', $result['title']);
346 $this->assertEquals('', $result['description']);
347 $this->assertEquals('', $result['tags']);
348 $this->assertEquals(false, $result['private']);
349 $this->assertEquals($created, $result['created']);
350 $this->assertTrue(new \DateTime('5 seconds ago') < $result['updated']);
351 }
352}
diff --git a/tests/api/controllers/DeleteLinkTest.php b/tests/api/controllers/DeleteLinkTest.php
new file mode 100644
index 00000000..7d797137
--- /dev/null
+++ b/tests/api/controllers/DeleteLinkTest.php
@@ -0,0 +1,126 @@
1<?php
2
3
4namespace Shaarli\Api\Controllers;
5
6use Shaarli\Config\ConfigManager;
7use Slim\Container;
8use Slim\Http\Environment;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12class DeleteLinkTest extends \PHPUnit_Framework_TestCase
13{
14 /**
15 * @var string datastore to test write operations
16 */
17 protected static $testDatastore = 'sandbox/datastore.php';
18
19 /**
20 * @var string datastore to test write operations
21 */
22 protected static $testHistory = 'sandbox/history.php';
23
24 /**
25 * @var ConfigManager instance
26 */
27 protected $conf;
28
29 /**
30 * @var \ReferenceLinkDB instance.
31 */
32 protected $refDB = null;
33
34 /**
35 * @var \LinkDB instance.
36 */
37 protected $linkDB;
38
39 /**
40 * @var \History instance.
41 */
42 protected $history;
43
44 /**
45 * @var Container instance.
46 */
47 protected $container;
48
49 /**
50 * @var Links controller instance.
51 */
52 protected $controller;
53
54 /**
55 * Before each test, instantiate a new Api with its config, plugins and links.
56 */
57 public function setUp()
58 {
59 $this->conf = new ConfigManager('tests/utils/config/configJson');
60 $this->refDB = new \ReferenceLinkDB();
61 $this->refDB->write(self::$testDatastore);
62 $this->linkDB = new \LinkDB(self::$testDatastore, true, false);
63 $refHistory = new \ReferenceHistory();
64 $refHistory->write(self::$testHistory);
65 $this->history = new \History(self::$testHistory);
66 $this->container = new Container();
67 $this->container['conf'] = $this->conf;
68 $this->container['db'] = $this->linkDB;
69 $this->container['history'] = $this->history;
70
71 $this->controller = new Links($this->container);
72 }
73
74 /**
75 * After each test, remove the test datastore.
76 */
77 public function tearDown()
78 {
79 @unlink(self::$testDatastore);
80 @unlink(self::$testHistory);
81 }
82
83 /**
84 * Test DELETE link endpoint: the link should be removed.
85 */
86 public function testDeleteLinkValid()
87 {
88 $id = '41';
89 $this->assertTrue(isset($this->linkDB[$id]));
90 $env = Environment::mock([
91 'REQUEST_METHOD' => 'DELETE',
92 ]);
93 $request = Request::createFromEnvironment($env);
94
95 $response = $this->controller->deleteLink($request, new Response(), ['id' => $id]);
96 $this->assertEquals(204, $response->getStatusCode());
97 $this->assertEmpty((string) $response->getBody());
98
99 $this->linkDB = new \LinkDB(self::$testDatastore, true, false);
100 $this->assertFalse(isset($this->linkDB[$id]));
101
102 $historyEntry = $this->history->getHistory()[0];
103 $this->assertEquals(\History::DELETED, $historyEntry['event']);
104 $this->assertTrue(
105 (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
106 );
107 $this->assertEquals($id, $historyEntry['id']);
108 }
109
110 /**
111 * Test DELETE link endpoint: reach not existing ID.
112 *
113 * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException
114 */
115 public function testDeleteLink404()
116 {
117 $id = -1;
118 $this->assertFalse(isset($this->linkDB[$id]));
119 $env = Environment::mock([
120 'REQUEST_METHOD' => 'DELETE',
121 ]);
122 $request = Request::createFromEnvironment($env);
123
124 $this->controller->deleteLink($request, new Response(), ['id' => $id]);
125 }
126}
diff --git a/tests/api/controllers/GetLinkIdTest.php b/tests/api/controllers/GetLinkIdTest.php
new file mode 100644
index 00000000..57528d5a
--- /dev/null
+++ b/tests/api/controllers/GetLinkIdTest.php
@@ -0,0 +1,132 @@
1<?php
2
3namespace Shaarli\Api\Controllers;
4
5use Shaarli\Config\ConfigManager;
6
7use Slim\Container;
8use Slim\Http\Environment;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Class GetLinkIdTest
14 *
15 * Test getLink by ID API service.
16 *
17 * @see http://shaarli.github.io/api-documentation/#links-link-get
18 *
19 * @package Shaarli\Api\Controllers
20 */
21class GetLinkIdTest extends \PHPUnit_Framework_TestCase
22{
23 /**
24 * @var string datastore to test write operations
25 */
26 protected static $testDatastore = 'sandbox/datastore.php';
27
28 /**
29 * @var ConfigManager instance
30 */
31 protected $conf;
32
33 /**
34 * @var \ReferenceLinkDB instance.
35 */
36 protected $refDB = null;
37
38 /**
39 * @var Container instance.
40 */
41 protected $container;
42
43 /**
44 * @var Links controller instance.
45 */
46 protected $controller;
47
48 /**
49 * Number of JSON fields per link.
50 */
51 const NB_FIELDS_LINK = 9;
52
53 /**
54 * Before each test, instantiate a new Api with its config, plugins and links.
55 */
56 public function setUp()
57 {
58 $this->conf = new ConfigManager('tests/utils/config/configJson');
59 $this->refDB = new \ReferenceLinkDB();
60 $this->refDB->write(self::$testDatastore);
61
62 $this->container = new Container();
63 $this->container['conf'] = $this->conf;
64 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
65 $this->container['history'] = null;
66
67 $this->controller = new Links($this->container);
68 }
69
70 /**
71 * After each test, remove the test datastore.
72 */
73 public function tearDown()
74 {
75 @unlink(self::$testDatastore);
76 }
77
78 /**
79 * Test basic getLink service: return link ID=41.
80 */
81 public function testGetLinkId()
82 {
83 // Used by index_url().
84 $_SERVER['SERVER_NAME'] = 'domain.tld';
85 $_SERVER['SERVER_PORT'] = 80;
86 $_SERVER['SCRIPT_NAME'] = '/';
87
88 $id = 41;
89 $env = Environment::mock([
90 'REQUEST_METHOD' => 'GET',
91 ]);
92 $request = Request::createFromEnvironment($env);
93
94 $response = $this->controller->getLink($request, new Response(), ['id' => $id]);
95 $this->assertEquals(200, $response->getStatusCode());
96 $data = json_decode((string) $response->getBody(), true);
97 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
98 $this->assertEquals($id, $data['id']);
99
100 // Check link elements
101 $this->assertEquals('http://domain.tld/?WDWyig', $data['url']);
102 $this->assertEquals('WDWyig', $data['shorturl']);
103 $this->assertEquals('Link title: @website', $data['title']);
104 $this->assertEquals(
105 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
106 $data['description']
107 );
108 $this->assertEquals('sTuff', $data['tags'][0]);
109 $this->assertEquals(false, $data['private']);
110 $this->assertEquals(
111 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM),
112 $data['created']
113 );
114 $this->assertEmpty($data['updated']);
115 }
116
117 /**
118 * Test basic getLink service: get non existent link => ApiLinkNotFoundException.
119 *
120 * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException
121 * @expectedExceptionMessage Link not found
122 */
123 public function testGetLink404()
124 {
125 $env = Environment::mock([
126 'REQUEST_METHOD' => 'GET',
127 ]);
128 $request = Request::createFromEnvironment($env);
129
130 $this->controller->getLink($request, new Response(), ['id' => -1]);
131 }
132}
diff --git a/tests/api/controllers/GetLinksTest.php b/tests/api/controllers/GetLinksTest.php
new file mode 100644
index 00000000..d22ed3bf
--- /dev/null
+++ b/tests/api/controllers/GetLinksTest.php
@@ -0,0 +1,472 @@
1<?php
2namespace Shaarli\Api\Controllers;
3
4use Shaarli\Config\ConfigManager;
5
6use Slim\Container;
7use Slim\Http\Environment;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class GetLinksTest
13 *
14 * Test get Link list REST API service.
15 *
16 * @see http://shaarli.github.io/api-documentation/#links-links-collection-get
17 *
18 * @package Shaarli\Api\Controllers
19 */
20class GetLinksTest extends \PHPUnit_Framework_TestCase
21{
22 /**
23 * @var string datastore to test write operations
24 */
25 protected static $testDatastore = 'sandbox/datastore.php';
26
27 /**
28 * @var ConfigManager instance
29 */
30 protected $conf;
31
32 /**
33 * @var \ReferenceLinkDB instance.
34 */
35 protected $refDB = null;
36
37 /**
38 * @var Container instance.
39 */
40 protected $container;
41
42 /**
43 * @var Links controller instance.
44 */
45 protected $controller;
46
47 /**
48 * Number of JSON field per link.
49 */
50 const NB_FIELDS_LINK = 9;
51
52 /**
53 * Before every test, instantiate a new Api with its config, plugins and links.
54 */
55 public function setUp()
56 {
57 $this->conf = new ConfigManager('tests/utils/config/configJson');
58 $this->refDB = new \ReferenceLinkDB();
59 $this->refDB->write(self::$testDatastore);
60
61 $this->container = new Container();
62 $this->container['conf'] = $this->conf;
63 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
64 $this->container['history'] = null;
65
66 $this->controller = new Links($this->container);
67 }
68
69 /**
70 * After every test, remove the test datastore.
71 */
72 public function tearDown()
73 {
74 @unlink(self::$testDatastore);
75 }
76
77 /**
78 * Test basic getLinks service: returns all links.
79 */
80 public function testGetLinks()
81 {
82 // Used by index_url().
83 $_SERVER['SERVER_NAME'] = 'domain.tld';
84 $_SERVER['SERVER_PORT'] = 80;
85 $_SERVER['SCRIPT_NAME'] = '/';
86
87 $env = Environment::mock([
88 'REQUEST_METHOD' => 'GET',
89 ]);
90 $request = Request::createFromEnvironment($env);
91
92 $response = $this->controller->getLinks($request, new Response());
93 $this->assertEquals(200, $response->getStatusCode());
94 $data = json_decode((string) $response->getBody(), true);
95 $this->assertEquals($this->refDB->countLinks(), count($data));
96
97 // Check order
98 $order = [41, 8, 6, 7, 0, 1, 9, 4, 42];
99 $cpt = 0;
100 foreach ($data as $link) {
101 $this->assertEquals(self::NB_FIELDS_LINK, count($link));
102 $this->assertEquals($order[$cpt++], $link['id']);
103 }
104
105 // Check first element fields
106 $first = $data[0];
107 $this->assertEquals('http://domain.tld/?WDWyig', $first['url']);
108 $this->assertEquals('WDWyig', $first['shorturl']);
109 $this->assertEquals('Link title: @website', $first['title']);
110 $this->assertEquals(
111 'Stallman has a beard and is part of the Free Software Foundation (or not). Seriously, read this. #hashtag',
112 $first['description']
113 );
114 $this->assertEquals('sTuff', $first['tags'][0]);
115 $this->assertEquals(false, $first['private']);
116 $this->assertEquals(
117 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20150310_114651')->format(\DateTime::ATOM),
118 $first['created']
119 );
120 $this->assertEmpty($first['updated']);
121
122 // Multi tags
123 $link = $data[1];
124 $this->assertEquals(7, count($link['tags']));
125
126 // Update date
127 $this->assertEquals(
128 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20160803_093033')->format(\DateTime::ATOM),
129 $link['updated']
130 );
131 }
132
133 /**
134 * Test getLinks service with offset and limit parameter:
135 * limit=1 and offset=1 should return only the second link, ID=8 (ordered by creation date DESC).
136 */
137 public function testGetLinksOffsetLimit()
138 {
139 $env = Environment::mock([
140 'REQUEST_METHOD' => 'GET',
141 'QUERY_STRING' => 'offset=1&limit=1'
142 ]);
143 $request = Request::createFromEnvironment($env);
144 $response = $this->controller->getLinks($request, new Response());
145 $this->assertEquals(200, $response->getStatusCode());
146 $data = json_decode((string) $response->getBody(), true);
147 $this->assertEquals(1, count($data));
148 $this->assertEquals(8, $data[0]['id']);
149 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
150 }
151
152 /**
153 * Test getLinks with limit=all (return all link).
154 */
155 public function testGetLinksLimitAll()
156 {
157 $env = Environment::mock([
158 'REQUEST_METHOD' => 'GET',
159 'QUERY_STRING' => 'limit=all'
160 ]);
161 $request = Request::createFromEnvironment($env);
162 $response = $this->controller->getLinks($request, new Response());
163 $this->assertEquals(200, $response->getStatusCode());
164 $data = json_decode((string) $response->getBody(), true);
165 $this->assertEquals($this->refDB->countLinks(), count($data));
166 // Check order
167 $order = [41, 8, 6, 7, 0, 1, 9, 4, 42];
168 $cpt = 0;
169 foreach ($data as $link) {
170 $this->assertEquals(self::NB_FIELDS_LINK, count($link));
171 $this->assertEquals($order[$cpt++], $link['id']);
172 }
173 }
174
175 /**
176 * Test getLinks service with offset and limit parameter:
177 * limit=1 and offset=1 should return only the second link, ID=8 (ordered by creation date DESC).
178 */
179 public function testGetLinksOffsetTooHigh()
180 {
181 $env = Environment::mock([
182 'REQUEST_METHOD' => 'GET',
183 'QUERY_STRING' => 'offset=100'
184 ]);
185 $request = Request::createFromEnvironment($env);
186 $response = $this->controller->getLinks($request, new Response());
187 $this->assertEquals(200, $response->getStatusCode());
188 $data = json_decode((string) $response->getBody(), true);
189 $this->assertEmpty(count($data));
190 }
191
192 /**
193 * Test getLinks with visibility parameter set to all
194 */
195 public function testGetLinksVisibilityAll()
196 {
197 $env = Environment::mock(
198 [
199 'REQUEST_METHOD' => 'GET',
200 'QUERY_STRING' => 'visibility=all'
201 ]
202 );
203 $request = Request::createFromEnvironment($env);
204 $response = $this->controller->getLinks($request, new Response());
205 $this->assertEquals(200, $response->getStatusCode());
206 $data = json_decode((string)$response->getBody(), true);
207 $this->assertEquals($this->refDB->countLinks(), count($data));
208 $this->assertEquals(41, $data[0]['id']);
209 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
210 }
211
212 /**
213 * Test getLinks with visibility parameter set to private
214 */
215 public function testGetLinksVisibilityPrivate()
216 {
217 $env = Environment::mock([
218 'REQUEST_METHOD' => 'GET',
219 'QUERY_STRING' => 'visibility=private'
220 ]);
221 $request = Request::createFromEnvironment($env);
222 $response = $this->controller->getLinks($request, new Response());
223 $this->assertEquals(200, $response->getStatusCode());
224 $data = json_decode((string) $response->getBody(), true);
225 $this->assertEquals($this->refDB->countPrivateLinks(), count($data));
226 $this->assertEquals(6, $data[0]['id']);
227 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
228 }
229
230 /**
231 * Test getLinks with visibility parameter set to public
232 */
233 public function testGetLinksVisibilityPublic()
234 {
235 $env = Environment::mock(
236 [
237 'REQUEST_METHOD' => 'GET',
238 'QUERY_STRING' => 'visibility=public'
239 ]
240 );
241 $request = Request::createFromEnvironment($env);
242 $response = $this->controller->getLinks($request, new Response());
243 $this->assertEquals(200, $response->getStatusCode());
244 $data = json_decode((string)$response->getBody(), true);
245 $this->assertEquals($this->refDB->countPublicLinks(), count($data));
246 $this->assertEquals(41, $data[0]['id']);
247 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
248 }
249
250 /**
251 * Test getLinks service with offset and limit parameter:
252 * limit=1 and offset=1 should return only the second link, ID=8 (ordered by creation date DESC).
253 */
254 public function testGetLinksSearchTerm()
255 {
256 // Only in description - 1 result
257 $env = Environment::mock([
258 'REQUEST_METHOD' => 'GET',
259 'QUERY_STRING' => 'searchterm=Tropical'
260 ]);
261 $request = Request::createFromEnvironment($env);
262 $response = $this->controller->getLinks($request, new Response());
263 $this->assertEquals(200, $response->getStatusCode());
264 $data = json_decode((string) $response->getBody(), true);
265 $this->assertEquals(1, count($data));
266 $this->assertEquals(1, $data[0]['id']);
267 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
268
269 // Only in tags - 1 result
270 $env = Environment::mock([
271 'REQUEST_METHOD' => 'GET',
272 'QUERY_STRING' => 'searchterm=tag3'
273 ]);
274 $request = Request::createFromEnvironment($env);
275 $response = $this->controller->getLinks($request, new Response());
276 $this->assertEquals(200, $response->getStatusCode());
277 $data = json_decode((string) $response->getBody(), true);
278 $this->assertEquals(1, count($data));
279 $this->assertEquals(0, $data[0]['id']);
280 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
281
282 // Multiple results (2)
283 $env = Environment::mock([
284 'REQUEST_METHOD' => 'GET',
285 'QUERY_STRING' => 'searchterm=stallman'
286 ]);
287 $request = Request::createFromEnvironment($env);
288 $response = $this->controller->getLinks($request, new Response());
289 $this->assertEquals(200, $response->getStatusCode());
290 $data = json_decode((string) $response->getBody(), true);
291 $this->assertEquals(2, count($data));
292 $this->assertEquals(41, $data[0]['id']);
293 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
294 $this->assertEquals(8, $data[1]['id']);
295 $this->assertEquals(self::NB_FIELDS_LINK, count($data[1]));
296
297 // Multiword - 2 results
298 $env = Environment::mock([
299 'REQUEST_METHOD' => 'GET',
300 'QUERY_STRING' => 'searchterm=stallman+software'
301 ]);
302 $request = Request::createFromEnvironment($env);
303 $response = $this->controller->getLinks($request, new Response());
304 $this->assertEquals(200, $response->getStatusCode());
305 $data = json_decode((string) $response->getBody(), true);
306 $this->assertEquals(2, count($data));
307 $this->assertEquals(41, $data[0]['id']);
308 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
309 $this->assertEquals(8, $data[1]['id']);
310 $this->assertEquals(self::NB_FIELDS_LINK, count($data[1]));
311
312 // URL encoding
313 $env = Environment::mock([
314 'REQUEST_METHOD' => 'GET',
315 'QUERY_STRING' => 'searchterm='. urlencode('@web')
316 ]);
317 $request = Request::createFromEnvironment($env);
318 $response = $this->controller->getLinks($request, new Response());
319 $this->assertEquals(200, $response->getStatusCode());
320 $data = json_decode((string) $response->getBody(), true);
321 $this->assertEquals(2, count($data));
322 $this->assertEquals(41, $data[0]['id']);
323 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
324 $this->assertEquals(8, $data[1]['id']);
325 $this->assertEquals(self::NB_FIELDS_LINK, count($data[1]));
326 }
327
328 public function testGetLinksSearchTermNoResult()
329 {
330 $env = Environment::mock([
331 'REQUEST_METHOD' => 'GET',
332 'QUERY_STRING' => 'searchterm=nope'
333 ]);
334 $request = Request::createFromEnvironment($env);
335 $response = $this->controller->getLinks($request, new Response());
336 $this->assertEquals(200, $response->getStatusCode());
337 $data = json_decode((string) $response->getBody(), true);
338 $this->assertEquals(0, count($data));
339 }
340
341 public function testGetLinksSearchTags()
342 {
343 // Single tag
344 $env = Environment::mock([
345 'REQUEST_METHOD' => 'GET',
346 'QUERY_STRING' => 'searchtags=dev',
347 ]);
348 $request = Request::createFromEnvironment($env);
349 $response = $this->controller->getLinks($request, new Response());
350 $this->assertEquals(200, $response->getStatusCode());
351 $data = json_decode((string) $response->getBody(), true);
352 $this->assertEquals(2, count($data));
353 $this->assertEquals(0, $data[0]['id']);
354 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
355 $this->assertEquals(4, $data[1]['id']);
356 $this->assertEquals(self::NB_FIELDS_LINK, count($data[1]));
357
358 // Multitag + exclude
359 $env = Environment::mock([
360 'REQUEST_METHOD' => 'GET',
361 'QUERY_STRING' => 'searchtags=stuff+-gnu',
362 ]);
363 $request = Request::createFromEnvironment($env);
364 $response = $this->controller->getLinks($request, new Response());
365 $this->assertEquals(200, $response->getStatusCode());
366 $data = json_decode((string) $response->getBody(), true);
367 $this->assertEquals(1, count($data));
368 $this->assertEquals(41, $data[0]['id']);
369 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
370
371 // wildcard: placeholder at the start
372 $env = Environment::mock([
373 'REQUEST_METHOD' => 'GET',
374 'QUERY_STRING' => 'searchtags=*Tuff',
375 ]);
376 $request = Request::createFromEnvironment($env);
377 $response = $this->controller->getLinks($request, new Response());
378 $this->assertEquals(200, $response->getStatusCode());
379 $data = json_decode((string) $response->getBody(), true);
380 $this->assertEquals(2, count($data));
381 $this->assertEquals(41, $data[0]['id']);
382
383 // wildcard: placeholder at the end
384 $env = Environment::mock([
385 'REQUEST_METHOD' => 'GET',
386 'QUERY_STRING' => 'searchtags=c*',
387 ]);
388 $request = Request::createFromEnvironment($env);
389 $response = $this->controller->getLinks($request, new Response());
390 $this->assertEquals(200, $response->getStatusCode());
391 $data = json_decode((string) $response->getBody(), true);
392 $this->assertEquals(4, count($data));
393 $this->assertEquals(6, $data[0]['id']);
394
395 // wildcard: placeholder at the middle
396 $env = Environment::mock([
397 'REQUEST_METHOD' => 'GET',
398 'QUERY_STRING' => 'searchtags=w*b',
399 ]);
400 $request = Request::createFromEnvironment($env);
401 $response = $this->controller->getLinks($request, new Response());
402 $this->assertEquals(200, $response->getStatusCode());
403 $data = json_decode((string) $response->getBody(), true);
404 $this->assertEquals(4, count($data));
405 $this->assertEquals(6, $data[0]['id']);
406
407 // wildcard: match all
408 $env = Environment::mock([
409 'REQUEST_METHOD' => 'GET',
410 'QUERY_STRING' => 'searchtags=*',
411 ]);
412 $request = Request::createFromEnvironment($env);
413 $response = $this->controller->getLinks($request, new Response());
414 $this->assertEquals(200, $response->getStatusCode());
415 $data = json_decode((string) $response->getBody(), true);
416 $this->assertEquals(9, count($data));
417 $this->assertEquals(41, $data[0]['id']);
418
419 // wildcard: optional ('*' does not need to expand)
420 $env = Environment::mock([
421 'REQUEST_METHOD' => 'GET',
422 'QUERY_STRING' => 'searchtags=*stuff*',
423 ]);
424 $request = Request::createFromEnvironment($env);
425 $response = $this->controller->getLinks($request, new Response());
426 $this->assertEquals(200, $response->getStatusCode());
427 $data = json_decode((string) $response->getBody(), true);
428 $this->assertEquals(2, count($data));
429 $this->assertEquals(41, $data[0]['id']);
430
431 // wildcard: exclusions
432 $env = Environment::mock([
433 'REQUEST_METHOD' => 'GET',
434 'QUERY_STRING' => 'searchtags=*a*+-*e*',
435 ]);
436 $request = Request::createFromEnvironment($env);
437 $response = $this->controller->getLinks($request, new Response());
438 $this->assertEquals(200, $response->getStatusCode());
439 $data = json_decode((string) $response->getBody(), true);
440 $this->assertEquals(1, count($data));
441 $this->assertEquals(41, $data[0]['id']); // finds '#hashtag' in descr.
442
443 // wildcard: exclude all
444 $env = Environment::mock([
445 'REQUEST_METHOD' => 'GET',
446 'QUERY_STRING' => 'searchtags=-*',
447 ]);
448 $request = Request::createFromEnvironment($env);
449 $response = $this->controller->getLinks($request, new Response());
450 $this->assertEquals(200, $response->getStatusCode());
451 $data = json_decode((string) $response->getBody(), true);
452 $this->assertEquals(0, count($data));
453 }
454
455 /**
456 * Test getLinks service with search tags+terms.
457 */
458 public function testGetLinksSearchTermsAndTags()
459 {
460 $env = Environment::mock([
461 'REQUEST_METHOD' => 'GET',
462 'QUERY_STRING' => 'searchterm=poke&searchtags=dev',
463 ]);
464 $request = Request::createFromEnvironment($env);
465 $response = $this->controller->getLinks($request, new Response());
466 $this->assertEquals(200, $response->getStatusCode());
467 $data = json_decode((string) $response->getBody(), true);
468 $this->assertEquals(1, count($data));
469 $this->assertEquals(0, $data[0]['id']);
470 $this->assertEquals(self::NB_FIELDS_LINK, count($data[0]));
471 }
472}
diff --git a/tests/api/controllers/HistoryTest.php b/tests/api/controllers/HistoryTest.php
new file mode 100644
index 00000000..61046d97
--- /dev/null
+++ b/tests/api/controllers/HistoryTest.php
@@ -0,0 +1,216 @@
1<?php
2
3
4namespace Shaarli\Api\Controllers;
5
6
7use Shaarli\Config\ConfigManager;
8use Slim\Container;
9use Slim\Http\Environment;
10use Slim\Http\Request;
11use Slim\Http\Response;
12
13require_once 'tests/utils/ReferenceHistory.php';
14
15class HistoryTest extends \PHPUnit_Framework_TestCase
16{
17 /**
18 * @var string datastore to test write operations
19 */
20 protected static $testHistory = 'sandbox/history.php';
21
22 /**
23 * @var ConfigManager instance
24 */
25 protected $conf;
26
27 /**
28 * @var \ReferenceHistory instance.
29 */
30 protected $refHistory = null;
31
32 /**
33 * @var Container instance.
34 */
35 protected $container;
36
37 /**
38 * @var History controller instance.
39 */
40 protected $controller;
41
42 /**
43 * Before every test, instantiate a new Api with its config, plugins and links.
44 */
45 public function setUp()
46 {
47 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
48 $this->refHistory = new \ReferenceHistory();
49 $this->refHistory->write(self::$testHistory);
50 $this->container = new Container();
51 $this->container['conf'] = $this->conf;
52 $this->container['db'] = true;
53 $this->container['history'] = new \History(self::$testHistory);
54
55 $this->controller = new History($this->container);
56 }
57
58 /**
59 * After every test, remove the test datastore.
60 */
61 public function tearDown()
62 {
63 @unlink(self::$testHistory);
64 }
65
66 /**
67 * Test /history service without parameter.
68 */
69 public function testGetHistory()
70 {
71 $env = Environment::mock([
72 'REQUEST_METHOD' => 'GET',
73 ]);
74 $request = Request::createFromEnvironment($env);
75
76 $response = $this->controller->getHistory($request, new Response());
77 $this->assertEquals(200, $response->getStatusCode());
78 $data = json_decode((string) $response->getBody(), true);
79
80 $this->assertEquals($this->refHistory->count(), count($data));
81
82 $this->assertEquals(\History::DELETED, $data[0]['event']);
83 $this->assertEquals(
84 \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
85 $data[0]['datetime']
86 );
87 $this->assertEquals(124, $data[0]['id']);
88
89 $this->assertEquals(\History::SETTINGS, $data[1]['event']);
90 $this->assertEquals(
91 \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
92 $data[1]['datetime']
93 );
94 $this->assertNull($data[1]['id']);
95
96 $this->assertEquals(\History::UPDATED, $data[2]['event']);
97 $this->assertEquals(
98 \DateTime::createFromFormat('Ymd_His', '20170301_121214')->format(\DateTime::ATOM),
99 $data[2]['datetime']
100 );
101 $this->assertEquals(123, $data[2]['id']);
102
103 $this->assertEquals(\History::CREATED, $data[3]['event']);
104 $this->assertEquals(
105 \DateTime::createFromFormat('Ymd_His', '20170201_121214')->format(\DateTime::ATOM),
106 $data[3]['datetime']
107 );
108 $this->assertEquals(124, $data[3]['id']);
109
110 $this->assertEquals(\History::CREATED, $data[4]['event']);
111 $this->assertEquals(
112 \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
113 $data[4]['datetime']
114 );
115 $this->assertEquals(123, $data[4]['id']);
116 }
117
118 /**
119 * Test /history service with limit parameter.
120 */
121 public function testGetHistoryLimit()
122 {
123 $env = Environment::mock([
124 'REQUEST_METHOD' => 'GET',
125 'QUERY_STRING' => 'limit=1'
126 ]);
127 $request = Request::createFromEnvironment($env);
128
129 $response = $this->controller->getHistory($request, new Response());
130 $this->assertEquals(200, $response->getStatusCode());
131 $data = json_decode((string) $response->getBody(), true);
132
133 $this->assertEquals(1, count($data));
134
135 $this->assertEquals(\History::DELETED, $data[0]['event']);
136 $this->assertEquals(
137 \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
138 $data[0]['datetime']
139 );
140 $this->assertEquals(124, $data[0]['id']);
141 }
142
143 /**
144 * Test /history service with offset parameter.
145 */
146 public function testGetHistoryOffset()
147 {
148 $env = Environment::mock([
149 'REQUEST_METHOD' => 'GET',
150 'QUERY_STRING' => 'offset=4'
151 ]);
152 $request = Request::createFromEnvironment($env);
153
154 $response = $this->controller->getHistory($request, new Response());
155 $this->assertEquals(200, $response->getStatusCode());
156 $data = json_decode((string) $response->getBody(), true);
157
158 $this->assertEquals(1, count($data));
159
160 $this->assertEquals(\History::CREATED, $data[0]['event']);
161 $this->assertEquals(
162 \DateTime::createFromFormat('Ymd_His', '20170101_121212')->format(\DateTime::ATOM),
163 $data[0]['datetime']
164 );
165 $this->assertEquals(123, $data[0]['id']);
166 }
167
168 /**
169 * Test /history service with since parameter.
170 */
171 public function testGetHistorySince()
172 {
173 $env = Environment::mock([
174 'REQUEST_METHOD' => 'GET',
175 'QUERY_STRING' => 'since=2017-03-03T00:00:00%2B00:00'
176 ]);
177 $request = Request::createFromEnvironment($env);
178
179 $response = $this->controller->getHistory($request, new Response());
180 $this->assertEquals(200, $response->getStatusCode());
181 $data = json_decode((string) $response->getBody(), true);
182
183 $this->assertEquals(1, count($data));
184
185 $this->assertEquals(\History::DELETED, $data[0]['event']);
186 $this->assertEquals(
187 \DateTime::createFromFormat('Ymd_His', '20170303_121216')->format(\DateTime::ATOM),
188 $data[0]['datetime']
189 );
190 $this->assertEquals(124, $data[0]['id']);
191 }
192
193 /**
194 * Test /history service with since parameter.
195 */
196 public function testGetHistorySinceOffsetLimit()
197 {
198 $env = Environment::mock([
199 'REQUEST_METHOD' => 'GET',
200 'QUERY_STRING' => 'since=2017-02-01T00:00:00%2B00:00&offset=1&limit=1'
201 ]);
202 $request = Request::createFromEnvironment($env);
203
204 $response = $this->controller->getHistory($request, new Response());
205 $this->assertEquals(200, $response->getStatusCode());
206 $data = json_decode((string) $response->getBody(), true);
207
208 $this->assertEquals(1, count($data));
209
210 $this->assertEquals(\History::SETTINGS, $data[0]['event']);
211 $this->assertEquals(
212 \DateTime::createFromFormat('Ymd_His', '20170302_121215')->format(\DateTime::ATOM),
213 $data[0]['datetime']
214 );
215 }
216}
diff --git a/tests/api/controllers/InfoTest.php b/tests/api/controllers/InfoTest.php
new file mode 100644
index 00000000..f7e63bfa
--- /dev/null
+++ b/tests/api/controllers/InfoTest.php
@@ -0,0 +1,115 @@
1<?php
2namespace Shaarli\Api\Controllers;
3
4use Shaarli\Config\ConfigManager;
5
6use Slim\Container;
7use Slim\Http\Environment;
8use Slim\Http\Request;
9use Slim\Http\Response;
10
11/**
12 * Class InfoTest
13 *
14 * Test REST API controller Info.
15 *
16 * @package Api\Controllers
17 */
18class InfoTest extends \PHPUnit_Framework_TestCase
19{
20 /**
21 * @var string datastore to test write operations
22 */
23 protected static $testDatastore = 'sandbox/datastore.php';
24
25 /**
26 * @var ConfigManager instance
27 */
28 protected $conf;
29
30 /**
31 * @var \ReferenceLinkDB instance.
32 */
33 protected $refDB = null;
34
35 /**
36 * @var Container instance.
37 */
38 protected $container;
39
40 /**
41 * @var Info controller instance.
42 */
43 protected $controller;
44
45 /**
46 * Before every test, instantiate a new Api with its config, plugins and links.
47 */
48 public function setUp()
49 {
50 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
51 $this->refDB = new \ReferenceLinkDB();
52 $this->refDB->write(self::$testDatastore);
53
54 $this->container = new Container();
55 $this->container['conf'] = $this->conf;
56 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
57 $this->container['history'] = null;
58
59 $this->controller = new Info($this->container);
60 }
61
62 /**
63 * After every test, remove the test datastore.
64 */
65 public function tearDown()
66 {
67 @unlink(self::$testDatastore);
68 }
69
70 /**
71 * Test /info service.
72 */
73 public function testGetInfo()
74 {
75 $env = Environment::mock([
76 'REQUEST_METHOD' => 'GET',
77 ]);
78 $request = Request::createFromEnvironment($env);
79
80 $response = $this->controller->getInfo($request, new Response());
81 $this->assertEquals(200, $response->getStatusCode());
82 $data = json_decode((string) $response->getBody(), true);
83
84 $this->assertEquals(\ReferenceLinkDB::$NB_LINKS_TOTAL, $data['global_counter']);
85 $this->assertEquals(2, $data['private_counter']);
86 $this->assertEquals('Shaarli', $data['settings']['title']);
87 $this->assertEquals('?', $data['settings']['header_link']);
88 $this->assertEquals('UTC', $data['settings']['timezone']);
89 $this->assertEquals(ConfigManager::$DEFAULT_PLUGINS, $data['settings']['enabled_plugins']);
90 $this->assertEquals(false, $data['settings']['default_private_links']);
91
92 $title = 'My links';
93 $headerLink = 'http://shaarli.tld';
94 $timezone = 'Europe/Paris';
95 $enabledPlugins = array('foo', 'bar');
96 $defaultPrivateLinks = true;
97 $this->conf->set('general.title', $title);
98 $this->conf->set('general.header_link', $headerLink);
99 $this->conf->set('general.timezone', $timezone);
100 $this->conf->set('general.enabled_plugins', $enabledPlugins);
101 $this->conf->set('privacy.default_private_links', $defaultPrivateLinks);
102
103 $response = $this->controller->getInfo($request, new Response());
104 $this->assertEquals(200, $response->getStatusCode());
105 $data = json_decode((string) $response->getBody(), true);
106
107 $this->assertEquals(\ReferenceLinkDB::$NB_LINKS_TOTAL, $data['global_counter']);
108 $this->assertEquals(2, $data['private_counter']);
109 $this->assertEquals($title, $data['settings']['title']);
110 $this->assertEquals($headerLink, $data['settings']['header_link']);
111 $this->assertEquals($timezone, $data['settings']['timezone']);
112 $this->assertEquals($enabledPlugins, $data['settings']['enabled_plugins']);
113 $this->assertEquals($defaultPrivateLinks, $data['settings']['default_private_links']);
114 }
115}
diff --git a/tests/api/controllers/PostLinkTest.php b/tests/api/controllers/PostLinkTest.php
new file mode 100644
index 00000000..31954e39
--- /dev/null
+++ b/tests/api/controllers/PostLinkTest.php
@@ -0,0 +1,216 @@
1<?php
2
3namespace Shaarli\Api\Controllers;
4
5
6use Shaarli\Config\ConfigManager;
7use Slim\Container;
8use Slim\Http\Environment;
9use Slim\Http\Request;
10use Slim\Http\Response;
11
12/**
13 * Class PostLinkTest
14 *
15 * Test POST Link REST API service.
16 *
17 * @package Shaarli\Api\Controllers
18 */
19class PostLinkTest extends \PHPUnit_Framework_TestCase
20{
21 /**
22 * @var string datastore to test write operations
23 */
24 protected static $testDatastore = 'sandbox/datastore.php';
25
26 /**
27 * @var string datastore to test write operations
28 */
29 protected static $testHistory = 'sandbox/history.php';
30
31 /**
32 * @var ConfigManager instance
33 */
34 protected $conf;
35
36 /**
37 * @var \ReferenceLinkDB instance.
38 */
39 protected $refDB = null;
40
41 /**
42 * @var \History instance.
43 */
44 protected $history;
45
46 /**
47 * @var Container instance.
48 */
49 protected $container;
50
51 /**
52 * @var Links controller instance.
53 */
54 protected $controller;
55
56 /**
57 * Number of JSON field per link.
58 */
59 const NB_FIELDS_LINK = 9;
60
61 /**
62 * Before every test, instantiate a new Api with its config, plugins and links.
63 */
64 public function setUp()
65 {
66 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
67 $this->refDB = new \ReferenceLinkDB();
68 $this->refDB->write(self::$testDatastore);
69
70 $refHistory = new \ReferenceHistory();
71 $refHistory->write(self::$testHistory);
72 $this->history = new \History(self::$testHistory);
73
74 $this->container = new Container();
75 $this->container['conf'] = $this->conf;
76 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
77 $this->container['history'] = new \History(self::$testHistory);
78
79 $this->controller = new Links($this->container);
80
81 $mock = $this->getMock('\Slim\Router', ['relativePathFor']);
82 $mock->expects($this->any())
83 ->method('relativePathFor')
84 ->willReturn('api/v1/links/1');
85
86 // affect @property-read... seems to work
87 $this->controller->getCi()->router = $mock;
88
89 // Used by index_url().
90 $this->controller->getCi()['environment'] = [
91 'SERVER_NAME' => 'domain.tld',
92 'SERVER_PORT' => 80,
93 'SCRIPT_NAME' => '/',
94 ];
95 }
96
97 /**
98 * After every test, remove the test datastore.
99 */
100 public function tearDown()
101 {
102 @unlink(self::$testDatastore);
103 @unlink(self::$testHistory);
104 }
105
106 /**
107 * Test link creation without any field: creates a blank note.
108 */
109 public function testPostLinkMinimal()
110 {
111 $env = Environment::mock([
112 'REQUEST_METHOD' => 'POST',
113 ]);
114
115 $request = Request::createFromEnvironment($env);
116
117 $response = $this->controller->postLink($request, new Response());
118 $this->assertEquals(201, $response->getStatusCode());
119 $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
120 $data = json_decode((string) $response->getBody(), true);
121 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
122 $this->assertEquals(43, $data['id']);
123 $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
124 $this->assertEquals('http://domain.tld/?' . $data['shorturl'], $data['url']);
125 $this->assertEquals('?' . $data['shorturl'], $data['title']);
126 $this->assertEquals('', $data['description']);
127 $this->assertEquals([], $data['tags']);
128 $this->assertEquals(false, $data['private']);
129 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
130 $this->assertEquals('', $data['updated']);
131
132 $historyEntry = $this->history->getHistory()[0];
133 $this->assertEquals(\History::CREATED, $historyEntry['event']);
134 $this->assertTrue(
135 (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
136 );
137 $this->assertEquals(43, $historyEntry['id']);
138 }
139
140 /**
141 * Test link creation with all available fields.
142 */
143 public function testPostLinkFull()
144 {
145 $link = [
146 'url' => 'website.tld/test?foo=bar',
147 'title' => 'new entry',
148 'description' => 'shaare description',
149 'tags' => ['one', 'two'],
150 'private' => true,
151 ];
152 $env = Environment::mock([
153 'REQUEST_METHOD' => 'POST',
154 'CONTENT_TYPE' => 'application/json'
155 ]);
156
157 $request = Request::createFromEnvironment($env);
158 $request = $request->withParsedBody($link);
159 $response = $this->controller->postLink($request, new Response());
160
161 $this->assertEquals(201, $response->getStatusCode());
162 $this->assertEquals('api/v1/links/1', $response->getHeader('Location')[0]);
163 $data = json_decode((string) $response->getBody(), true);
164 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
165 $this->assertEquals(43, $data['id']);
166 $this->assertRegExp('/[\w-_]{6}/', $data['shorturl']);
167 $this->assertEquals('http://' . $link['url'], $data['url']);
168 $this->assertEquals($link['title'], $data['title']);
169 $this->assertEquals($link['description'], $data['description']);
170 $this->assertEquals($link['tags'], $data['tags']);
171 $this->assertEquals(true, $data['private']);
172 $this->assertTrue(new \DateTime('2 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['created']));
173 $this->assertEquals('', $data['updated']);
174 }
175
176 /**
177 * Test link creation with an existing link (duplicate URL). Should return a 409 HTTP error and the existing link.
178 */
179 public function testPostLinkDuplicate()
180 {
181 $link = [
182 'url' => 'mediagoblin.org/',
183 'title' => 'new entry',
184 'description' => 'shaare description',
185 'tags' => ['one', 'two'],
186 'private' => true,
187 ];
188 $env = Environment::mock([
189 'REQUEST_METHOD' => 'POST',
190 'CONTENT_TYPE' => 'application/json'
191 ]);
192
193 $request = Request::createFromEnvironment($env);
194 $request = $request->withParsedBody($link);
195 $response = $this->controller->postLink($request, new Response());
196
197 $this->assertEquals(409, $response->getStatusCode());
198 $data = json_decode((string) $response->getBody(), true);
199 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
200 $this->assertEquals(7, $data['id']);
201 $this->assertEquals('IuWvgA', $data['shorturl']);
202 $this->assertEquals('http://mediagoblin.org/', $data['url']);
203 $this->assertEquals('MediaGoblin', $data['title']);
204 $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']);
205 $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
206 $this->assertEquals(false, $data['private']);
207 $this->assertEquals(
208 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
209 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
210 );
211 $this->assertEquals(
212 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
213 \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
214 );
215 }
216}
diff --git a/tests/api/controllers/PutLinkTest.php b/tests/api/controllers/PutLinkTest.php
new file mode 100644
index 00000000..8a562571
--- /dev/null
+++ b/tests/api/controllers/PutLinkTest.php
@@ -0,0 +1,222 @@
1<?php
2
3
4namespace Shaarli\Api\Controllers;
5
6
7use Shaarli\Config\ConfigManager;
8use Slim\Container;
9use Slim\Http\Environment;
10use Slim\Http\Request;
11use Slim\Http\Response;
12
13class PutLinkTest extends \PHPUnit_Framework_TestCase
14{
15 /**
16 * @var string datastore to test write operations
17 */
18 protected static $testDatastore = 'sandbox/datastore.php';
19
20 /**
21 * @var string datastore to test write operations
22 */
23 protected static $testHistory = 'sandbox/history.php';
24
25 /**
26 * @var ConfigManager instance
27 */
28 protected $conf;
29
30 /**
31 * @var \ReferenceLinkDB instance.
32 */
33 protected $refDB = null;
34
35 /**
36 * @var \History instance.
37 */
38 protected $history;
39
40 /**
41 * @var Container instance.
42 */
43 protected $container;
44
45 /**
46 * @var Links controller instance.
47 */
48 protected $controller;
49
50 /**
51 * Number of JSON field per link.
52 */
53 const NB_FIELDS_LINK = 9;
54
55 /**
56 * Before every test, instantiate a new Api with its config, plugins and links.
57 */
58 public function setUp()
59 {
60 $this->conf = new ConfigManager('tests/utils/config/configJson.json.php');
61 $this->refDB = new \ReferenceLinkDB();
62 $this->refDB->write(self::$testDatastore);
63
64 $refHistory = new \ReferenceHistory();
65 $refHistory->write(self::$testHistory);
66 $this->history = new \History(self::$testHistory);
67
68 $this->container = new Container();
69 $this->container['conf'] = $this->conf;
70 $this->container['db'] = new \LinkDB(self::$testDatastore, true, false);
71 $this->container['history'] = new \History(self::$testHistory);
72
73 $this->controller = new Links($this->container);
74
75 // Used by index_url().
76 $this->controller->getCi()['environment'] = [
77 'SERVER_NAME' => 'domain.tld',
78 'SERVER_PORT' => 80,
79 'SCRIPT_NAME' => '/',
80 ];
81 }
82
83 /**
84 * After every test, remove the test datastore.
85 */
86 public function tearDown()
87 {
88 @unlink(self::$testDatastore);
89 @unlink(self::$testHistory);
90 }
91
92 /**
93 * Test link update without value: reset the link to default values
94 */
95 public function testPutLinkMinimal()
96 {
97 $env = Environment::mock([
98 'REQUEST_METHOD' => 'PUT',
99 ]);
100 $id = '41';
101 $request = Request::createFromEnvironment($env);
102
103 $response = $this->controller->putLink($request, new Response(), ['id' => $id]);
104 $this->assertEquals(200, $response->getStatusCode());
105 $data = json_decode((string) $response->getBody(), true);
106 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
107 $this->assertEquals($id, $data['id']);
108 $this->assertEquals('WDWyig', $data['shorturl']);
109 $this->assertEquals('http://domain.tld/?WDWyig', $data['url']);
110 $this->assertEquals('?WDWyig', $data['title']);
111 $this->assertEquals('', $data['description']);
112 $this->assertEquals([], $data['tags']);
113 $this->assertEquals(false, $data['private']);
114 $this->assertEquals(
115 \DateTime::createFromFormat('Ymd_His', '20150310_114651'),
116 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
117 );
118 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']));
119
120 $historyEntry = $this->history->getHistory()[0];
121 $this->assertEquals(\History::UPDATED, $historyEntry['event']);
122 $this->assertTrue(
123 (new \DateTime())->add(\DateInterval::createFromDateString('-5 seconds')) < $historyEntry['datetime']
124 );
125 $this->assertEquals($id, $historyEntry['id']);
126 }
127
128 /**
129 * Test link update with new values
130 */
131 public function testPutLinkWithValues()
132 {
133 $env = Environment::mock([
134 'REQUEST_METHOD' => 'PUT',
135 'CONTENT_TYPE' => 'application/json'
136 ]);
137 $id = 41;
138 $update = [
139 'url' => 'http://somewhere.else',
140 'title' => 'Le Cid',
141 'description' => 'Percé jusques au fond du cœur [...]',
142 'tags' => ['corneille', 'rodrigue'],
143 'private' => true,
144 ];
145 $request = Request::createFromEnvironment($env);
146 $request = $request->withParsedBody($update);
147
148 $response = $this->controller->putLink($request, new Response(), ['id' => $id]);
149 $this->assertEquals(200, $response->getStatusCode());
150 $data = json_decode((string) $response->getBody(), true);
151 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
152 $this->assertEquals($id, $data['id']);
153 $this->assertEquals('WDWyig', $data['shorturl']);
154 $this->assertEquals('http://somewhere.else', $data['url']);
155 $this->assertEquals('Le Cid', $data['title']);
156 $this->assertEquals('Percé jusques au fond du cœur [...]', $data['description']);
157 $this->assertEquals(['corneille', 'rodrigue'], $data['tags']);
158 $this->assertEquals(true, $data['private']);
159 $this->assertEquals(
160 \DateTime::createFromFormat('Ymd_His', '20150310_114651'),
161 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
162 );
163 $this->assertTrue(new \DateTime('5 seconds ago') < \DateTime::createFromFormat(\DateTime::ATOM, $data['updated']));
164 }
165
166 /**
167 * Test link update with an existing URL: 409 Conflict with the existing link as body
168 */
169 public function testPutLinkDuplicate()
170 {
171 $link = [
172 'url' => 'mediagoblin.org/',
173 'title' => 'new entry',
174 'description' => 'shaare description',
175 'tags' => ['one', 'two'],
176 'private' => true,
177 ];
178 $env = Environment::mock([
179 'REQUEST_METHOD' => 'PUT',
180 'CONTENT_TYPE' => 'application/json'
181 ]);
182
183 $request = Request::createFromEnvironment($env);
184 $request = $request->withParsedBody($link);
185 $response = $this->controller->putLink($request, new Response(), ['id' => 41]);
186
187 $this->assertEquals(409, $response->getStatusCode());
188 $data = json_decode((string) $response->getBody(), true);
189 $this->assertEquals(self::NB_FIELDS_LINK, count($data));
190 $this->assertEquals(7, $data['id']);
191 $this->assertEquals('IuWvgA', $data['shorturl']);
192 $this->assertEquals('http://mediagoblin.org/', $data['url']);
193 $this->assertEquals('MediaGoblin', $data['title']);
194 $this->assertEquals('A free software media publishing platform #hashtagOther', $data['description']);
195 $this->assertEquals(['gnu', 'media', 'web', '.hidden', 'hashtag'], $data['tags']);
196 $this->assertEquals(false, $data['private']);
197 $this->assertEquals(
198 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130614_184135'),
199 \DateTime::createFromFormat(\DateTime::ATOM, $data['created'])
200 );
201 $this->assertEquals(
202 \DateTime::createFromFormat(\LinkDB::LINK_DATE_FORMAT, '20130615_184230'),
203 \DateTime::createFromFormat(\DateTime::ATOM, $data['updated'])
204 );
205 }
206
207 /**
208 * Test link update on non existent link => ApiLinkNotFoundException.
209 *
210 * @expectedException Shaarli\Api\Exceptions\ApiLinkNotFoundException
211 * @expectedExceptionMessage Link not found
212 */
213 public function testGetLink404()
214 {
215 $env = Environment::mock([
216 'REQUEST_METHOD' => 'PUT',
217 ]);
218 $request = Request::createFromEnvironment($env);
219
220 $this->controller->putLink($request, new Response(), ['id' => -1]);
221 }
222}