API improvements (#1826)

This commit is contained in:
Kevin Papst
2020-07-17 20:30:16 +02:00
committed by GitHub
parent 5864c7e127
commit c843dba29a
77 changed files with 1745 additions and 1189 deletions

View File

@@ -12,6 +12,7 @@ namespace App\Tests\API;
use App\DataFixtures\UserFixtures;
use App\Entity\User;
use App\Tests\Controller\ControllerBaseTest;
use PHPUnit\Framework\Constraint\IsType;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\HttpKernelBrowser;
@@ -240,7 +241,7 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
/**
* @param Response $response
* @param string[] $failedFields
* @param bool $extraFields
* @param bool $extraFields test for the error "This form should not contain extra fields"
*/
protected function assertApiCallValidationError(Response $response, array $failedFields, bool $extraFields = false)
{
@@ -258,8 +259,366 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
$data = $result['errors']['children'];
foreach ($failedFields as $fieldName) {
self::assertArrayHasKey($fieldName, $data);
self::assertArrayHasKey('errors', $data[$fieldName]);
self::assertArrayHasKey($fieldName, $data, sprintf('Could not find validation error for field: %s', $fieldName));
self::assertArrayHasKey('errors', $data[$fieldName], sprintf('Field %s has no validation problem', $fieldName));
}
$foundErrors = [];
foreach ($data as $fieldName => $field) {
if (\array_key_exists('errors', $field) && \count($field['errors']) > 0) {
$foundErrors[$fieldName] = \count($field['errors']);
}
}
self::assertEquals(\count($failedFields), \count($foundErrors), 'Expected and actual validation error amount differs');
}
protected static function getExpectedResponseStructure(string $type): array
{
switch ($type) {
case 'TagEntity':
return [
'id' => 'int',
'name' => 'string',
'color' => 'string',
];
// embedded meta data
case 'CustomerMeta':
case 'ProjectMeta':
case 'ActivityMeta':
case 'TimesheetMeta':
return [
'name' => 'string',
'value' => 'string',
];
// if a user is embedded in other objects
case 'User':
// if a list of users is loaded
case 'UserCollection':
return [
'id' => 'int',
'username' => 'string',
'enabled' => 'bool',
'alias' => '@string',
];
// if a user is loaded explicitly
case 'UserEntity':
return [
'id' => 'int',
'username' => 'string',
'enabled' => 'bool',
'alias' => '@string',
'title' => '@string',
'avatar' => '@string',
'teams' => ['result' => 'array', 'type' => 'Team'],
'roles' => ['result' => 'array', 'type' => 'string'],
'language' => 'string',
'timezone' => 'string',
];
// if a team is embedded
case 'Team':
// if a collection of teams is requested
case 'TeamCollection':
return [
'id' => 'int',
'name' => 'string',
];
// explicitly requested team
case 'TeamEntity':
return [
'id' => 'int',
'name' => 'string',
'teamlead' => ['result' => 'object', 'type' => 'User'],
'users' => ['result' => 'array', 'type' => 'User'],
'customers' => ['result' => 'array', 'type' => '@Customer'],
'projects' => ['result' => 'array', 'type' => '@Project'],
];
// if a customer is embedded in other objects
case 'Customer':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'color' => '@string',
];
// if a list of customers is loaded
case 'CustomerCollection':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'boolean',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'CustomerMeta'],
'teams' => ['result' => 'array', 'type' => 'Team'],
'currency' => 'string', // since 1.10
];
// if a customer is loaded explicitly
case 'CustomerEntity':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'CustomerMeta'],
'teams' => ['result' => 'array', 'type' => 'Team'],
'homepage' => '@string',
'number' => '@string',
'comment' => '@string',
'company' => '@string',
'contact' => '@string',
'address' => '@string',
'country' => 'string',
'currency' => 'string',
'phone' => '@string',
'fax' => '@string',
'mobile' => '@string',
'email' => '@string',
'timezone' => 'string',
'budget' => 'float',
'timeBudget' => 'int',
'vatId' => '@string', // since 1.10
];
// if a project is embedded
case 'Project':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'color' => '@string',
'customer' => 'int',
];
// if a project is embedded in an expanded collection (here timesheet)
case 'ProjectExpanded':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'color' => '@string',
'customer' => ['result' => 'object', 'type' => 'Customer'],
];
// if a collection of projects is loaded
case 'ProjectCollection':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'customer' => 'int',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
'parentTitle' => 'string',
'start' => '@datetime',
'end' => '@datetime',
'teams' => ['result' => 'array', 'type' => 'Team'],
];
// if a project is explicitly loaded
case 'ProjectEntity':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'customer' => 'int',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
'parentTitle' => 'string',
'start' => '@datetime',
'end' => '@datetime',
'teams' => ['result' => 'array', 'type' => 'Team'],
'comment' => '@string',
'budget' => 'float',
'timeBudget' => 'int',
'orderNumber' => '@string',
'orderDate' => '@datetime',
];
// embedded activities
case 'Activity':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'project' => '@int',
'color' => '@string',
];
// collection of activities
case 'ActivityCollection':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'project' => '@int',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
'parentTitle' => '@string',
];
// if a activity is explicitly loaded
case 'ActivityEntity':
return [
'id' => 'int',
'name' => 'string',
'visible' => 'bool',
'project' => '@int',
'color' => '@string',
'metaFields' => ['result' => 'array', 'type' => 'ProjectMeta'],
'parentTitle' => '@string',
'comment' => '@string',
'budget' => 'float',
'timeBudget' => 'int',
];
case 'TimesheetEntity':
return [
'id' => 'int',
'begin' => 'DateTime',
'end' => '@DateTime',
'duration' => '@int',
'description' => '@string',
'rate' => 'float',
'activity' => 'int',
'project' => 'int',
'tags' => ['result' => 'array', 'type' => 'string'],
'user' => 'int',
'metaFields' => ['result' => 'array', 'type' => 'TimesheetMeta'],
'internalRate' => 'float',
'exported' => 'bool',
'fixedRate' => '@float',
'hourlyRate' => '@float',
// TODO new fields: billable, category
];
case 'TimesheetCollection':
return [
'id' => 'int',
'begin' => 'DateTime',
'end' => '@DateTime',
'duration' => '@int',
'description' => '@string',
'rate' => 'float',
'activity' => 'int',
'project' => 'int',
'tags' => ['result' => 'array', 'type' => 'string'],
'user' => 'int',
'metaFields' => ['result' => 'array', 'type' => 'TimesheetMeta'],
'internalRate' => 'float',
];
case 'TimesheetCollectionFull':
return [
'id' => 'int',
'begin' => 'DateTime',
'end' => '@DateTime',
'duration' => '@int',
'description' => '@string',
'rate' => 'float',
'activity' => ['result' => 'object', 'type' => 'Activity'],
'project' => ['result' => 'object', 'type' => 'ProjectExpanded'],
'tags' => ['result' => 'array', 'type' => 'string'],
'user' => 'int',
'metaFields' => ['result' => 'array', 'type' => 'TimesheetMeta'],
'internalRate' => 'float',
];
default:
throw new \Exception(sprintf('Unknown API response type: %s', $type));
}
}
/**
* The $type is either one of the types configured in config/packages/nelmio_api_doc.yaml or the class name.
*
* @param string $type
* @param array $result
* @throws \Exception
*/
protected function assertApiResponseTypeStructure(string $type, array $result)
{
$expected = self::getExpectedResponseStructure($type);
$expectedKeys = array_keys($expected);
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
self::assertEquals($expectedKeys, $actual, sprintf('Structure for API response type "%s" does not match', $type));
self::assertEquals(
\count($actual),
\count($expectedKeys),
sprintf('Mismatch between expected and result keys for API response type "%s". Expected %s keys but found %s.', $type, \count($expected), \count($actual))
);
foreach ($expected as $key => $value) {
if (\is_array($value)) {
switch ($value['result']) {
case 'array':
foreach ($result[$key] as $subResult) {
if ($value['type'] === 'string') {
self::assertIsString($subResult);
} else {
self::assertIsArray($subResult);
if ($value['type'][0] === '@') {
if (empty($result[$key])) {
continue;
}
$value['type'] = substr($value['type'], 1);
}
self::assertApiResponseTypeStructure($value['type'], $subResult);
}
}
break;
case 'object':
self::assertIsArray($result[$key], sprintf('Key "%s" in type "%s" is not an array', $key, $type));
if ($value['type'][0] === '@') {
if (empty($result[$key])) {
break;
}
$value['type'] = substr($value['type'], 1);
}
self::assertApiResponseTypeStructure($value['type'], $result[$key]);
break;
default:
throw new \Exception(sprintf('Invalid result type "%s" for subresource given', $value['result']));
}
continue;
}
if ($value[0] === '@') {
if (\is_null($result[$key])) {
continue;
}
$value = substr($value, 1);
}
if (strtolower($value) === 'datetime') {
// TODO
$value = 'string';
}
static::assertThat(
$result[$key],
new IsType($value),
sprintf('Found type mismatch in structure for API response type %s. Expected type "%s" for key "%s".', $type, $value, $key)
);
}
}
}

View File

@@ -11,6 +11,7 @@ namespace App\Tests\API;
use App\DataFixtures\UserFixtures;
use App\Entity\Activity;
use App\Entity\ActivityMeta;
use App\Entity\ActivityRate;
use App\Entity\Customer;
use App\Entity\Project;
@@ -103,6 +104,12 @@ class ActivityControllerTest extends APIControllerBaseTest
$em->persist($activity);
$activity = (new Activity())->setName('fifth one')->setComment('5')->setProject($project2);
$meta = new ActivityMeta();
$meta->setName('bar')->setValue('foo')->setIsVisible(false);
$activity->setMetaField($meta);
$meta = new ActivityMeta();
$meta->setName('foo')->setValue('bar')->setIsVisible(true);
$activity->setMetaField($meta);
$em->persist($activity);
$activity = (new Activity())->setName('sixth one')->setComment('6')->setVisible(false);
@@ -127,7 +134,7 @@ class ActivityControllerTest extends APIControllerBaseTest
for ($i = 0; $i < \count($result); $i++) {
$activity = $result[$i];
$hasProject = $expected[$i][0];
$this->assertStructure($activity, false);
self::assertApiResponseTypeStructure('ActivityCollection', $activity);
if ($hasProject) {
$this->assertEquals($expected[$i][1], $activity['project']);
}
@@ -161,7 +168,7 @@ class ActivityControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(5, \count($result));
$this->assertStructure($result[0], false);
self::assertApiResponseTypeStructure('ActivityCollection', $result[0]);
$this->assertEquals(1, $result[4]['project']);
$this->assertEquals(2, $result[3]['project']);
$this->assertEquals(2, $result[2]['project']);
@@ -174,7 +181,7 @@ class ActivityControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result, true);
self::assertApiResponseTypeStructure('ActivityEntity', $result);
}
public function testNotFound()
@@ -197,7 +204,7 @@ class ActivityControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ActivityEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -212,7 +219,7 @@ class ActivityControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ActivityEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -262,7 +269,7 @@ class ActivityControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ActivityEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -351,23 +358,4 @@ class ActivityControllerTest extends APIControllerBaseTest
$activity = $em->getRepository(Activity::class)->find(1);
$this->assertEquals('another,testing,bar', $activity->getMetaField('metatestmock')->getValue());
}
protected function assertStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'name', 'visible', 'project', 'color', 'metaFields', 'parentTitle'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, [
'comment', 'budget', 'timeBudget'
]);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
$this->assertEquals($expectedKeys, $actual, 'Activity structure does not match');
}
}

View File

@@ -27,6 +27,24 @@ class ApiDocControllerTest extends ControllerBaseTest
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/doc');
$this->assertStringContainsString('<title>Kimai 2 - API Docs</title>', $client->getResponse()->getContent());
$result = $client->getCrawler()->filter('script#swagger-data');
$swaggerJson = json_decode($result->text(), true);
$tags = [];
foreach ($swaggerJson['spec']['paths'] as $path) {
foreach ($path as $method) {
foreach ($method['tags'] as $tag) {
$tags[$tag] = $tag;
}
}
}
$expectedKeys = ['Activity', 'Default', 'Customer', 'Project', 'Tag', 'Team', 'Timesheet', 'User'];
$actual = array_keys($tags);
sort($actual);
sort($expectedKeys);
self::assertEquals($expectedKeys, $actual, sprintf('Expected %s sections in API docs, but found %s.', \count($actual), \count($expectedKeys)));
}
public function testGetJsonDocs()

View File

@@ -10,8 +10,12 @@
namespace App\Tests\API;
use App\DataFixtures\UserFixtures;
use App\Entity\Activity;
use App\Entity\Customer;
use App\Entity\CustomerMeta;
use App\Entity\CustomerRate;
use App\Entity\Project;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\CustomerRateRepository;
use App\Repository\CustomerRepository;
@@ -84,12 +88,12 @@ class CustomerControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(1, \count($result));
$this->assertStructure($result[0], false);
self::assertApiResponseTypeStructure('CustomerCollection', $result[0]);
}
public function testGetCollectionWithQuery()
{
$query = ['order' => 'ASC', 'orderBy' => 'name', 'visible' => 3];
$query = ['order' => 'ASC', 'orderBy' => 'name', 'visible' => 3, 'term' => 'test'];
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->assertAccessIsGranted($client, '/api/customers', 'GET', $query);
$result = json_decode($client->getResponse()->getContent(), true);
@@ -97,7 +101,7 @@ class CustomerControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(1, \count($result));
$this->assertStructure($result[0], false);
self::assertApiResponseTypeStructure('CustomerCollection', $result[0]);
}
public function testGetEntity()
@@ -107,7 +111,51 @@ class CustomerControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result, true);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
}
public function testGetEntityWithFullResponse()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $this->getEntityManager();
/** @var Customer $customer */
$customer = $em->getRepository(Customer::class)->find(1);
// add meta fields
$meta = new CustomerMeta();
$meta->setName('bar')->setValue('foo')->setIsVisible(false);
$customer->setMetaField($meta);
$meta = new CustomerMeta();
$meta->setName('foo')->setValue('bar')->setIsVisible(true);
$customer->setMetaField($meta);
$em->persist($customer);
// add a new project ...
$project = new Project();
$project->setName('Activity Test');
$project->setCustomer($customer);
$em->persist($project);
// ... with activity
$activity = (new Activity())->setName('first one')->setComment('1')->setProject($project);
$em->persist($activity);
// and finally a team
$team = new Team();
$team->setName('Testing customer 1 team');
$team->setTeamLead($this->getUserByRole(User::ROLE_USER));
$team->addCustomer($customer);
$team->addProject($project);
$team->addUser($this->getUserByRole(User::ROLE_TEAMLEAD));
$em->persist($team);
$this->assertAccessIsGranted($client, '/api/customers/1');
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
}
public function testNotFound()
@@ -132,7 +180,7 @@ class CustomerControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -150,7 +198,7 @@ class CustomerControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -206,7 +254,7 @@ class CustomerControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('CustomerEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -252,6 +300,13 @@ class CustomerControllerTest extends APIControllerBaseTest
$this->assertApiCallValidationError($response, ['currency']);
}
public function testMetaActionNotAllowed()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->request($client, '/api/customers/1/meta', 'PATCH', [], json_encode(['name' => 'asdasd']));
$this->assertApiResponseAccessDenied($client->getResponse(), 'You are not allowed to update this customer');
}
public function testMetaActionThrowsNotFound()
{
$this->assertEntityNotFoundForPatch(User::ROLE_ADMIN, '/api/customers/42/meta', []);
@@ -299,24 +354,4 @@ class CustomerControllerTest extends APIControllerBaseTest
$customer = $em->getRepository(Customer::class)->find(1);
$this->assertEquals('another,testing,bar', $customer->getMetaField('metatestmock')->getValue());
}
protected function assertStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'name', 'visible', 'color', 'metaFields', 'teams'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, [
'homepage', 'number', 'comment', 'company', 'contact', 'address', 'country', 'currency',
'phone', 'fax', 'mobile', 'email', 'timezone', 'budget', 'timeBudget'
]);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
$this->assertEquals($expectedKeys, $actual, 'Customer structure does not match');
}
}

View File

@@ -0,0 +1,32 @@
<?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\Model;
use App\API\Model\I18nConfig;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\API\Model\I18nConfig
*/
class I18nConfigTest extends TestCase
{
public function testSetter()
{
$sut = new I18nConfig();
$this->assertInstanceOf(I18nConfig::class, $sut->setIs24hours(false));
$this->assertInstanceOf(I18nConfig::class, $sut->setDuration('foo'));
$this->assertInstanceOf(I18nConfig::class, $sut->setDate('bar'));
$this->assertInstanceOf(I18nConfig::class, $sut->setDateTime('hello'));
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDate('world'));
$this->assertInstanceOf(I18nConfig::class, $sut->setFormDateTime('testing'));
$this->assertInstanceOf(I18nConfig::class, $sut->setTime('fun'));
}
}

View File

@@ -1,52 +0,0 @@
<?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\Model;
use App\API\Model\I18n;
use PHPUnit\Framework\TestCase;
/**
* @covers \App\API\Model\I18n
*/
class I18nTest extends TestCase
{
public function testDefaultValues()
{
$sut = new I18n();
$this->assertTrue($sut->isIs24hours());
$this->assertEquals('', $sut->getDuration());
$this->assertEquals('', $sut->getDate());
$this->assertEquals('', $sut->getDateTime());
$this->assertEquals('', $sut->getFormDate());
$this->assertEquals('', $sut->getFormDateTime());
$this->assertEquals('', $sut->getTime());
}
public function testSetter()
{
$sut = new I18n();
$this->assertInstanceOf(I18n::class, $sut->setIs24hours(false));
$this->assertInstanceOf(I18n::class, $sut->setDuration('foo'));
$this->assertInstanceOf(I18n::class, $sut->setDate('bar'));
$this->assertInstanceOf(I18n::class, $sut->setDateTime('hello'));
$this->assertInstanceOf(I18n::class, $sut->setFormDate('world'));
$this->assertInstanceOf(I18n::class, $sut->setFormDateTime('testing'));
$this->assertInstanceOf(I18n::class, $sut->setTime('fun'));
$this->assertFalse($sut->isIs24hours());
$this->assertEquals('foo', $sut->getDuration());
$this->assertEquals('bar', $sut->getDate());
$this->assertEquals('hello', $sut->getDateTime());
$this->assertEquals('world', $sut->getFormDate());
$this->assertEquals('testing', $sut->getFormDateTime());
$this->assertEquals('fun', $sut->getTime());
}
}

View File

@@ -17,17 +17,6 @@ use PHPUnit\Framework\TestCase;
*/
class TimesheetConfigTest extends TestCase
{
public function testDefaultValues()
{
$sut = new TimesheetConfig();
$this->assertTrue($sut->isAllowFutureTimes());
$this->assertTrue($sut->isAllowOverlapping());
$this->assertEquals('now', $sut->getDefaultBeginTime());
$this->assertEquals('default', $sut->getTrackingMode());
$this->assertEquals(1, $sut->getActiveEntriesSoftLimit());
$this->assertEquals(1, $sut->getActiveEntriesHardLimit());
}
public function testSetter()
{
$sut = new TimesheetConfig();
@@ -38,12 +27,5 @@ class TimesheetConfigTest extends TestCase
$this->assertInstanceOf(TimesheetConfig::class, $sut->setTrackingMode('punch'));
$this->assertInstanceOf(TimesheetConfig::class, $sut->setActiveEntriesSoftLimit(2));
$this->assertInstanceOf(TimesheetConfig::class, $sut->setActiveEntriesHardLimit(3));
$this->assertFalse($sut->isAllowFutureTimes());
$this->assertFalse($sut->isAllowOverlapping());
$this->assertEquals('08:00', $sut->getDefaultBeginTime());
$this->assertEquals('punch', $sut->getTrackingMode());
$this->assertEquals(2, $sut->getActiveEntriesSoftLimit());
$this->assertEquals(3, $sut->getActiveEntriesHardLimit());
}
}

View File

@@ -12,7 +12,9 @@ namespace App\Tests\API;
use App\DataFixtures\UserFixtures;
use App\Entity\Customer;
use App\Entity\Project;
use App\Entity\ProjectMeta;
use App\Entity\ProjectRate;
use App\Entity\Team;
use App\Entity\User;
use App\Repository\ProjectRateRepository;
use App\Repository\ProjectRepository;
@@ -86,7 +88,7 @@ class ProjectControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(1, \count($result));
$this->assertStructure($result[0], false);
self::assertApiResponseTypeStructure('ProjectCollection', $result[0]);
}
protected function loadProjectTestData(HttpKernelBrowser $client)
@@ -114,8 +116,25 @@ class ProjectControllerTest extends APIControllerBaseTest
$em->persist($project);
$project = (new Project())->setName('fifth')->setVisible(true)->setCustomer($customer);
// add meta fields
$meta = new ProjectMeta();
$meta->setName('bar')->setValue('foo')->setIsVisible(false);
$project->setMetaField($meta);
$meta = new ProjectMeta();
$meta->setName('foo')->setValue('bar')->setIsVisible(true);
$project->setMetaField($meta);
$em->persist($project);
// and a team
$team = new Team();
$team->setName('Testing project team');
$team->setTeamLead($this->getUserByRole(User::ROLE_USER));
$team->addCustomer($customer);
$team->addProject($project);
$team->addUser($this->getUserByRole(User::ROLE_TEAMLEAD));
$em->persist($team);
$project = (new Project())->setName('sixth')->setVisible(false)->setCustomer($customer3);
$em->persist($project);
@@ -138,7 +157,7 @@ class ProjectControllerTest extends APIControllerBaseTest
for ($i = 0; $i < \count($expected); $i++) {
$project = $result[$i];
$compare = $expected[$i];
$this->assertStructure($project, false);
self::assertApiResponseTypeStructure('ProjectCollection', $project);
$this->assertEquals($compare[1], $project['customer']);
}
}
@@ -169,7 +188,7 @@ class ProjectControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ProjectEntity', $result);
}
public function testNotFound()
@@ -195,7 +214,7 @@ class ProjectControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ProjectEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertEquals('2018-02-08T13:02:54+0000', $result['orderDate']);
self::assertEquals('2019-02-01T19:32:17+0000', $result['start']);
@@ -214,7 +233,7 @@ class ProjectControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ProjectEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertEquals('foo', $result['name']);
}
@@ -265,7 +284,7 @@ class ProjectControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('ProjectEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -354,23 +373,4 @@ class ProjectControllerTest extends APIControllerBaseTest
$project = $em->getRepository(Project::class)->find(1);
$this->assertEquals('another,testing,bar', $project->getMetaField('metatestmock')->getValue());
}
protected function assertStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'name', 'visible', 'customer', 'color', 'metaFields', 'parentTitle', 'start', 'end', 'teams'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, [
'comment', 'budget', 'timeBudget', 'orderNumber', 'orderDate'
]);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
$this->assertEquals($expectedKeys, $actual, 'Project structure does not match');
}
}

View File

@@ -192,6 +192,15 @@ trait RateControllerTestTrait
$this->assertNotFoundForDelete($client, $this->getRateUrl(2, 1));
}
public function testDeleteNotAllowed()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importTestRates(1);
$this->request($client, $this->getRateUrl(1, 1), 'DELETE');
$this->assertApiResponseAccessDenied($client->getResponse(), 'Access denied.');
}
protected function assertRateStructure(array $result, $user = null)
{
$expectedKeys = [

View File

@@ -66,14 +66,30 @@ class TagControllerTest extends APIControllerBaseTest
$this->importTagFixtures($client);
$data = [
'name' => 'foo',
'color' => '#000FFF'
];
$this->request($client, '/api/tags', 'POST', [], json_encode($data));
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TagEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertEquals('#000FFF', $result['color']);
}
public function testPostActionWithValidationErrors()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$this->importTagFixtures($client);
$data = [
'name' => '1',
'color' => '11231231231',
];
$this->request($client, '/api/tags', 'POST', [], json_encode($data));
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['name', 'color']);
}
public function testPostActionWithInvalidUser()
@@ -128,21 +144,4 @@ class TagControllerTest extends APIControllerBaseTest
{
$this->assertEntityNotFoundForDelete(User::ROLE_ADMIN, '/api/tags/255');
}
protected function assertStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'name', 'color'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, []);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
$this->assertEquals($expectedKeys, $actual, 'Tag structure does not match');
}
}

View File

@@ -59,7 +59,7 @@ class TeamControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
self::assertEquals(2, \count($result));
$this->assertStructure($result[0], false);
self::assertApiResponseTypeStructure('TeamCollection', $result[0]);
}
public function testGetEntity()
@@ -70,7 +70,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result, true);
self::assertApiResponseTypeStructure('TeamEntity', $result);
}
public function testNotFound()
@@ -95,7 +95,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
$this->assertNotEmpty($result['id']);
}
@@ -114,6 +114,20 @@ class TeamControllerTest extends APIControllerBaseTest
self::assertEquals('Access denied.', $json['message']);
}
public function testPostActionWithValidationErrors()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$data = [
'name' => '',
'teamlead' => 9999,
];
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['name', 'teamlead']);
}
public function testPatchAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
@@ -135,11 +149,34 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertCount(4, $result['users']);
}
public function testPatchActionWithValidationErrors()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$data = [
'name' => 'foo',
'teamlead' => 1,
];
$this->request($client, '/api/teams', 'POST', [], json_encode($data));
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$data = [
'name' => '1',
'teamlead' => 9999,
'users' => [9999]
];
$this->request($client, '/api/teams/' . $result['id'], 'PATCH', [], json_encode($data));
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['name', 'teamlead', 'users']);
}
public function testDeleteAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
@@ -148,7 +185,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
$this->assertNotEmpty($result['id']);
$id = $result['id'];
@@ -175,7 +212,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(2, $result['users']);
}
@@ -238,7 +275,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(3, $result['users']);
}
@@ -300,7 +337,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(1, $result['customers']);
self::assertEquals(1, $result['customers'][0]['id']);
}
@@ -381,7 +418,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(0, $result['customers']);
}
@@ -427,13 +464,12 @@ class TeamControllerTest extends APIControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
self::assertCount(0, $result['projects']);
$this->request($client, '/api/teams/' . $result['id'] . '/projects/1', 'POST');
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(1, $result['projects']);
self::assertEquals(1, $result['projects'][0]['id']);
}
@@ -520,7 +556,7 @@ class TeamControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('TeamEntity', $result);
self::assertCount(0, $result['projects']);
}
@@ -554,23 +590,4 @@ class TeamControllerTest extends APIControllerBaseTest
$json = json_decode($client->getResponse()->getContent(), true);
self::assertEquals('Project is not assigned to the team', $json['message']);
}
protected function assertStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'name'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, [
'teamlead', 'users', 'customers', 'projects'
]);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
self::assertEquals($expectedKeys, $actual, 'Team structure does not match');
}
}

View File

@@ -59,7 +59,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(10, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testGetCollectionFull()
@@ -72,8 +72,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(10, \count($result));
$this->assertDefaultStructure($result[0], false);
$this->assertHasSubresources($result[0]);
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
}
public function testGetCollectionForOtherUser()
@@ -99,14 +98,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(10, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testGetCollectionForAllUser()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$fixture = new TimesheetFixtures();
$fixture
@@ -125,7 +123,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(17, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testGetCollectionForEmptyResult()
@@ -170,7 +168,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(5, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testGetCollectionWithSingleParamsQuery()
@@ -202,14 +200,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(5, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testExportedFilter()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$fixture = new TimesheetFixtures();
$fixture
@@ -240,7 +237,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(7, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
$query = [
'page' => 1,
@@ -256,7 +253,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(10, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
$query = [
'page' => 1,
@@ -270,7 +267,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(17, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testGetEntity()
@@ -281,7 +278,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
}
public function testGetEntityAccessDenied()
@@ -301,7 +298,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
}
public function testGetEntityNotFound()
@@ -327,7 +324,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied
$this->assertEquals(2016, $result['rate']);
@@ -357,7 +354,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$this->assertEquals($user->getId(), $result['user']);
$this->assertNotEquals($admin->getId(), $result['user']);
@@ -437,7 +434,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$this->assertEquals(25200, $result['duration']);
$this->assertEquals(1, $result['exported']);
@@ -447,7 +444,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$fixture = new TimesheetFixtures();
$fixture
@@ -510,7 +506,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$id = $result['id'];
@@ -585,10 +581,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
}
public function testGetRecentCollectionWithSubresources()
public function testGetRecentAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_TEAMLEAD);
$em = $this->getEntityManager();
$start = new \DateTime('-10 days');
@@ -614,14 +609,12 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(1, \count($result));
$this->assertDefaultStructure($result[0], false);
$this->assertHasSubresources($result[0]);
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $result[0]);
}
public function testActiveAction()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $this->getEntityManager();
$start = new \DateTime('-10 days');
@@ -642,7 +635,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$results = json_decode($client->getResponse()->getContent(), true);
$this->assertEquals(3, \count($results));
foreach ($results as $timesheet) {
$this->assertDefaultStructure($timesheet, false);
self::assertApiResponseTypeStructure('TimesheetCollectionFull', $timesheet);
}
}
@@ -650,7 +643,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$start = new \DateTime('-10 days');
@@ -668,6 +660,11 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->request($client, '/api/timesheets/11/stop', 'PATCH');
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertNotEmpty($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$em = $this->getEntityManager();
/** @var Timesheet $timesheet */
$timesheet = $em->getRepository(Timesheet::class)->find(1);
@@ -692,7 +689,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$start = new \DateTime('-10 days');
@@ -715,7 +711,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->importFixtureForUser(User::ROLE_USER);
$em = $this->getEntityManager();
$fixture = new TimesheetFixtures();
$fixture
@@ -736,7 +731,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(5, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
$query = ['tags' => 'Test,Admin'];
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
@@ -745,7 +740,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(10, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
$query = ['tags' => 'Nothing-2-see,here'];
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
@@ -754,7 +749,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(20, \count($result));
$this->assertDefaultStructure($result[0], false);
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
}
public function testRestartAction()
@@ -772,7 +767,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertDefaultStructure($result, true);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertEmpty($result['description']);
$this->assertEmpty($result['tags']);
@@ -812,7 +807,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertDefaultStructure($result, true);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertEquals('foo', $result['description']);
$this->assertEquals([['name' => 'sdfsdf', 'value' => 'nnnnn'], ['name' => '1234567890', 'value' => '1234567890']], $result['metaFields']);
$this->assertEquals(['another', 'testing', 'bar'], $result['tags']);
@@ -832,8 +827,6 @@ class TimesheetControllerTest extends APIControllerBaseTest
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $this->getEntityManager();
$start = new \DateTime('-10 days');
$fixture = new TimesheetFixtures();
@@ -874,7 +867,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied
$this->assertEquals(2016, $result['rate']);
@@ -884,7 +877,7 @@ class TimesheetControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertDefaultStructure($result);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertNotEmpty($result['id']);
$this->assertTrue($result['duration'] == 57600 || $result['duration'] == 57660); // 1 minute rounding might be applied
$this->assertEquals(2016, $result['rate']);
@@ -907,7 +900,8 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->request($client, '/api/timesheets/1/export', 'PATCH');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertDefaultStructure(json_decode($client->getResponse()->getContent(), true), true);
$result = json_decode($client->getResponse()->getContent(), true);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$em->clear();
/** @var Timesheet $timesheet */
@@ -988,46 +982,13 @@ class TimesheetControllerTest extends APIControllerBaseTest
$this->assertTrue($client->getResponse()->isSuccessful());
$result = json_decode($client->getResponse()->getContent(), true);
self::assertApiResponseTypeStructure('TimesheetEntity', $result);
$this->assertEquals(['name' => 'metatestmock', 'value' => 'another,testing,bar'], $result['metaFields'][0]);
$em = $this->getEntityManager();
/** @var Timesheet $timesheet */
$timesheet = $em->getRepository(Timesheet::class)->find(1);
$this->assertEquals('another,testing,bar', $timesheet->getMetaField('metatestmock')->getValue());
}
protected function assertDefaultStructure(array $result, $full = true)
{
$expectedKeys = [
'id', 'begin', 'end', 'duration', 'description', 'rate', 'activity', 'project', 'tags', 'user', 'metaFields', 'internalRate'
];
if ($full) {
$expectedKeys = array_merge($expectedKeys, [
'exported', 'fixedRate', 'hourlyRate'
]);
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
$this->assertEquals($expectedKeys, $actual, 'Timesheet structure does not match');
}
protected function assertHasSubresources(array $result)
{
$this->assertArrayHasKey('activity', $result);
$this->assertArrayHasKey('id', $result['activity']);
$this->assertArrayHasKey('name', $result['activity']);
$this->assertArrayHasKey('visible', $result['activity']);
$this->assertArrayHasKey('project', $result['activity']);
$this->assertArrayHasKey('project', $result);
$this->assertArrayHasKey('id', $result['project']);
$this->assertArrayHasKey('name', $result['project']);
$this->assertArrayHasKey('visible', $result['project']);
$this->assertArrayHasKey('customer', $result['project']);
$this->assertArrayHasKey('id', $result['project']['customer']);
$this->assertArrayHasKey('name', $result['project']['customer']);
$this->assertArrayHasKey('visible', $result['project']['customer']);
}
}

View File

@@ -49,21 +49,21 @@ class UserControllerTest extends APIControllerBaseTest
$this->assertNotEmpty($result);
$this->assertEquals(7, \count($result));
foreach ($result as $user) {
$this->assertStructure($user, false);
self::assertApiResponseTypeStructure('UserCollection', $user);
}
}
public function testGetCollectionWithQuery()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/api/users', 'GET', ['visible' => 2, 'orderBy' => 'email', 'order' => 'DESC']);
$this->assertAccessIsGranted($client, '/api/users', 'GET', ['visible' => 2, 'orderBy' => 'email', 'order' => 'DESC', 'term' => 'chris']);
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertNotEmpty($result);
$this->assertEquals(1, \count($result));
foreach ($result as $user) {
$this->assertStructure($user, false);
self::assertApiResponseTypeStructure('UserCollection', $user);
}
}
@@ -77,7 +77,7 @@ class UserControllerTest extends APIControllerBaseTest
$this->assertNotEmpty($result);
$this->assertEquals(8, \count($result));
foreach ($result as $user) {
$this->assertStructure($user, false);
self::assertApiResponseTypeStructure('UserCollection', $user);
}
}
@@ -88,7 +88,7 @@ class UserControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('UserEntity', $result);
self::assertEquals('1', $result['id']);
self::assertEquals('CFO', $result['title']);
self::assertEquals('Clara Haynes', $result['alias']);
@@ -101,7 +101,7 @@ class UserControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('UserEntity', $result);
self::assertEquals('6', $result['id']);
self::assertEquals('Super Administrator', $result['title']);
self::assertEquals('', $result['alias']);
@@ -125,7 +125,7 @@ class UserControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('UserEntity', $result);
}
public function testPostAction()
@@ -150,7 +150,7 @@ class UserControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('UserEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertEquals('foo', $result['username']);
self::assertEquals('test123', $result['avatar']);
@@ -161,6 +161,50 @@ class UserControllerTest extends APIControllerBaseTest
self::assertEquals(['ROLE_TEAMLEAD', 'ROLE_ADMIN'], $result['roles']);
}
public function testPostActionWithShortPassword()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$data = [
'username' => 'foo',
'email' => 'foo@example.com',
'avatar' => 'test123',
'title' => 'asdfghjkl',
'plainPassword' => '1234567',
'enabled' => true,
'language' => 'ru',
'timezone' => 'Europe/Paris',
'roles' => [
'ROLE_TEAMLEAD',
'ROLE_ADMIN'
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['plainPassword']);
}
public function testPostActionWithValidationErrors()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$data = [
'username' => '',
'email' => '',
'plainPassword' => '123456',
'language' => 'xx',
'timezone' => 'XXX/YYY',
'roles' => [
'ABC',
],
];
$this->request($client, '/api/users', 'POST', [], json_encode($data));
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['username', 'email', 'plainPassword', 'language', 'timezone', 'roles']);
}
public function testPostActionWithInvalidUser()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
@@ -216,7 +260,7 @@ class UserControllerTest extends APIControllerBaseTest
$result = json_decode($client->getResponse()->getContent(), true);
$this->assertIsArray($result);
$this->assertStructure($result);
self::assertApiResponseTypeStructure('UserEntity', $result);
$this->assertNotEmpty($result['id']);
self::assertEquals('foo', $result['username']);
self::assertEquals('test321', $result['avatar']);
@@ -227,21 +271,36 @@ class UserControllerTest extends APIControllerBaseTest
self::assertEquals(['ROLE_TEAMLEAD'], $result['roles']);
}
protected function assertStructure(array $result, $full = true)
public function testPatchActionWithUnknownUser()
{
$expectedKeys = ['id', 'username', 'enabled', 'alias'];
$this->assertEntityNotFoundForPatch(User::ROLE_SUPER_ADMIN, '/api/users/255', []);
}
if ($full) {
$expectedKeys = array_merge(
$expectedKeys,
['title', 'avatar', 'teams', 'roles', 'language', 'timezone']
);
}
public function testPatchActionWithInvalidUser()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$this->request($client, '/api/users/1', 'PATCH', [], json_encode(['avatar' => 'asdasd']));
$this->assertApiResponseAccessDenied($client->getResponse(), 'Not allowed to edit user');
}
$actual = array_keys($result);
sort($actual);
sort($expectedKeys);
public function testPatchActionWithValidationErrors()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$data = [
'username' => '1', // not existing in form
'email' => '',
'plainPassword' => '123456', // not existing in form
'plainApiToken' => '123456', // not existing in form
'language' => 'xx',
'timezone' => 'XXX/YYY',
'roles' => [
'ABC',
],
];
$this->request($client, '/api/users/1', 'PATCH', [], json_encode($data));
$this->assertEquals($expectedKeys, $actual, 'User structure does not match');
$response = $client->getResponse();
$this->assertEquals(400, $response->getStatusCode());
$this->assertApiCallValidationError($response, ['email', 'language', 'timezone', 'roles'], true);
}
}