Added basic API endpoints (#258)

This commit is contained in:
Kevin Papst
2018-08-08 23:10:45 +02:00
committed by GitHub
parent a7780dac9e
commit 2f84f3751a
59 changed files with 3207 additions and 297 deletions

View File

@@ -0,0 +1,169 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\DataFixtures\UserFixtures;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Response;
/**
* Adds some useful functions for writing API integration tests.
*/
abstract class APIControllerBaseTest extends ControllerBaseTest
{
/**
* @param string $role
* @return Client
*/
protected function getClientForAuthenticatedUser(string $role = User::ROLE_USER)
{
switch ($role) {
case User::ROLE_SUPER_ADMIN:
$client = self::createClient([], [
'HTTP_X_AUTH_USER' => UserFixtures::USERNAME_SUPER_ADMIN,
'HTTP_X_AUTH_TOKEN' => UserFixtures::DEFAULT_API_TOKEN,
]);
break;
case User::ROLE_ADMIN:
$client = self::createClient([], [
'HTTP_X_AUTH_USER' => UserFixtures::USERNAME_ADMIN,
'HTTP_X_AUTH_TOKEN' => UserFixtures::DEFAULT_API_TOKEN,
]);
break;
case User::ROLE_TEAMLEAD:
$client = self::createClient([], [
'HTTP_X_AUTH_USER' => UserFixtures::USERNAME_TEAMLEAD,
'HTTP_X_AUTH_TOKEN' => UserFixtures::DEFAULT_API_TOKEN,
]);
break;
case User::ROLE_USER:
$client = self::createClient([], [
'HTTP_X_AUTH_USER' => UserFixtures::USERNAME_USER,
'HTTP_X_AUTH_TOKEN' => UserFixtures::DEFAULT_API_TOKEN,
]);
break;
default:
$client = null;
break;
}
return $client;
}
/**
* @param string $url
* @param bool $json
* @return string
*/
protected function createUrl($url, $json = true)
{
return '/' . ltrim($url, '/') . ($json ? '.json' : '');
}
/**
* @param Client $client
* @param string $url
* @param string $method
*/
protected function assertRequestIsSecured(Client $client, string $url, $method = 'GET')
{
$this->request($client, $url, $method);
$this->assertResponseIsSecured($client->getResponse(), $url);
}
/**
* @param Response $response
* @param string $url
*/
protected function assertResponseIsSecured(Response $response, string $url)
{
$data = ['message' => 'Authentication required, missing headers: X-AUTH-USER, X-AUTH-TOKEN'];
$this->assertEquals(
$data,
json_decode($response->getContent(), true),
sprintf('The secure URL %s is not protected.', $url)
);
$this->assertEquals(
Response::HTTP_FORBIDDEN, // TODO that should actually be Response::HTTP_UNAUTHORIZED
$response->getStatusCode(),
sprintf('The secure URL %s has the wrong status code %s.', $url, $response->getStatusCode())
);
}
/**
* @param string $role
* @param string $url
* @param string $method
*/
protected function assertUrlIsSecuredForRole(string $role, string $url, string $method = 'GET')
{
$client = $this->getClientForAuthenticatedUser($role);
$client->request($method, $this->createUrl($url));
$this->assertFalse(
$client->getResponse()->isSuccessful(),
sprintf('The secure URL %s is not protected for role %s', $url, $role)
);
$expected = [
'code' => 403,
'message' => 'Access denied.'
];
$this->assertEquals(403, $client->getResponse()->getStatusCode());
$this->assertEquals(
$expected,
json_decode($client->getResponse()->getContent(), true)
);
}
/**
* @param Client $client
* @param string $url
* @param string $method
* @return Crawler
*/
protected function request(Client $client, string $url, $method = 'GET')
{
return $client->request($method, $this->createUrl($url), [], [], ['HTTP_CONTENT_TYPE' => 'application/json']);
}
/**
* @param string $role
* @param string $url
*/
protected function assertEntityNotFound(string $role, string $url)
{
$client = $this->getClientForAuthenticatedUser($role);
$this->request($client, $url);
$expected = [
'code' => 404,
'message' => 'Not found'
];
$this->assertEquals(404, $client->getResponse()->getStatusCode());
$this->assertEquals(
$expected,
json_decode($client->getResponse()->getContent(), true)
);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
/**
* @coversDefaultClass \App\API\ActivityController
* @group integration
* @group legacy
*/
class ActivityControllerTest extends APIControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/activities');
}
public function testGetCollection()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/activities');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertNotEmpty($result);
$this->assertEquals(1, count($result));
$this->assertStructure($result[0]);
}
public function testGetEntity()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/activities/1');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertStructure($result);
}
public function testNotFound()
{
$this->assertEntityNotFound(User::ROLE_USER, '/api/activities/2');
}
protected function assertStructure(array $result)
{
$expectedKeys = [
'id', 'name', 'comment', 'visible', 'project_id'
];
$actual = array_keys($result);
$this->assertEquals(count($expectedKeys), count($actual), 'Activity entity has different amount of keys');
$this->assertEquals($expectedKeys, $actual, 'Activity structure does not match');
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
/**
* @group integration
* @group legacy
*/
class ApiDocControllerTest extends ControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/doc');
}
public function testGetDocs()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/doc');
$this->assertContains('<title>Kimai 2 - API Docs</title>', $client->getResponse()->getContent());
}
public function testGetJsonDocs()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/doc.json');
$this->assertContains('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertNotEmpty($result);
}
/**
* @param string $url
* @return string
*/
protected function createUrl($url)
{
return '/' . ltrim($url, '/');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
/**
* @coversDefaultClass \App\API\CustomerController
* @group integration
* @group legacy
*/
class CustomerControllerTest extends APIControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/customers');
}
public function testGetCollection()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/customers');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertNotEmpty($result);
$this->assertEquals(1, count($result));
$this->assertStructure($result[0]);
}
public function testGetEntity()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/customers/1');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertStructure($result);
}
public function testNotFound()
{
$this->assertEntityNotFound(User::ROLE_USER, '/api/customers/2');
}
protected function assertStructure(array $result)
{
$expectedKeys = [
'id', 'name', 'number', 'comment', 'visible', 'company', 'contact', 'address', 'country', 'currency',
'phone', 'fax', 'mobile', 'mail', 'timezone'
];
$actual = array_keys($result);
$this->assertEquals(count($expectedKeys), count($actual), 'Customer entity has different amount of keys');
$this->assertEquals($expectedKeys, $actual, 'Customer structure does not match');
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
/**
* @coversDefaultClass \App\API\HealthcheckController
* @group integration
* @group legacy
*/
class HealthcheckControllerTest extends APIControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/ping');
}
public function testPing()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/ping');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertEquals(['message' => 'pong'], $result);
}
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
/**
* @coversDefaultClass \App\API\ProjectController
* @group integration
* @group legacy
*/
class ProjectControllerTest extends APIControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/projects');
}
public function testGetCollection()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/projects');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertNotEmpty($result);
$this->assertEquals(1, count($result));
$this->assertStructure($result[0]);
}
public function testGetEntity()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/projects/1');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertStructure($result);
}
public function testNotFound()
{
$this->assertEntityNotFound(User::ROLE_USER, '/api/projects/2');
}
protected function assertStructure(array $result)
{
$expectedKeys = [
'id', 'name', 'comment', 'visible', 'budget', 'order_number', 'customer_id'
];
$actual = array_keys($result);
$this->assertEquals(count($expectedKeys), count($actual), 'Project entity has different amount of keys');
$this->assertEquals($expectedKeys, $actual, 'Project structure does not match');
}
}

View File

@@ -0,0 +1,65 @@
<?php
/*
* This file is part of the Kimai time-tracking app.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Tests\API;
use App\Entity\User;
/**
* @coversDefaultClass \App\API\UserController
* @group integration
* @group legacy
*/
class UserControllerTest extends APIControllerBaseTest
{
public function testIsSecure()
{
$this->assertUrlIsSecured('/api/users');
$this->assertUrlIsSecuredForRole(User::ROLE_ADMIN, '/api/users');
}
public function testGetCollection()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/api/users');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertNotEmpty($result);
$this->assertEquals(6, count($result));
$this->assertStructure($result[0]);
}
public function testGetEntity()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/api/users/1');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertInternalType('array', $result);
$this->assertStructure($result);
}
public function testNotFound()
{
$this->assertEntityNotFound(User::ROLE_SUPER_ADMIN, '/api/users/99');
}
protected function assertStructure(array $result)
{
$expectedKeys = [
'id', 'username', 'enabled', 'roles', 'alias', 'title', 'avatar'
];
$actual = array_keys($result);
$this->assertEquals(count($expectedKeys), count($actual), 'User entity has different amount of keys');
$this->assertEquals($expectedKeys, $actual, 'User structure does not match');
}
}