Release 2.14 (#4710)

- show "link has expired message" in password reset screen
- added date objects as hydrator variables - for custom date formats in invoice templates
- show meta-fields with null values (e.g. booleans with `false` where hidden)
- fix permission check: allow to remove `view_own_timesheet` but still record times
- prevent error 500 if customer country is empty
- fix API 500 error if project does not exist when creating new timesheet
- fix tags are not created in remote-search mode
- do not check "export items" by default
- fix daterange query, if user an request locale are different
- added logging for invalid SAML responses (see various discussions)
This commit is contained in:
Kevin Papst
2024-04-05 12:38:21 +02:00
committed by GitHub
parent 19b2d47591
commit b6c98f871d
45 changed files with 318 additions and 317 deletions

View File

@@ -465,7 +465,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'hourlyRate' => 127,
'billable' => false
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -494,7 +496,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'hourlyRate' => 127,
'billable' => true
];
$this->request($client, '/api/timesheets?full=true', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets?full=true', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -526,7 +530,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'end' => ($dateTime->createDateTime())->format('Y-m-d H:m:0'),
'description' => 'foo',
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -571,7 +577,60 @@ class TimesheetControllerTest extends APIControllerBaseTest
'end' => (new \DateTime())->format('Y-m-d H:m:s'),
'description' => 'foo',
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertApiCallValidationError($client->getResponse(), ['project']);
}
public function testPostActionWithUnknownActivity(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $this->getEntityManager();
$customer = new Customer('foo-bar-1');
$customer->setVisible(false);
$customer->setCountry('DE');
$customer->setTimezone('Europe/Berlin');
$em->persist($customer);
$project = new Project();
$project->setName('foo-bar-2');
$project->setVisible(true);
$project->setCustomer($customer);
$em->persist($project);
$data = [
'begin' => (new \DateTime('- 8 hours'))->format('Y-m-d H:m:s'),
'end' => (new \DateTime())->format('Y-m-d H:m:s'),
'project' => $project->getId(),
'activity' => 99,
];
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertApiCallValidationError($client->getResponse(), ['project']);
}
public function testPostActionWithNonExistingProject(): void
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_USER);
$em = $this->getEntityManager();
$activity = new Activity();
$activity->setName('foo-bar-3');
$activity->setVisible(true);
$em->persist($activity);
$em->flush();
$data = [
'begin' => (new \DateTime('- 8 hours'))->format('Y-m-d H:m:s'),
'end' => (new \DateTime())->format('Y-m-d H:m:s'),
'project' => 99,
'activity' => $activity->getId(),
];
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertApiCallValidationError($client->getResponse(), ['project']);
}
@@ -605,7 +664,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'end' => (new \DateTime())->format('Y-m-d H:m'),
'description' => 'foo',
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertApiCallValidationError($client->getResponse(), ['activity']);
}
@@ -635,7 +696,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'end' => (new \DateTime())->format('Y-m-d H:m'),
'description' => 'foo',
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -674,7 +737,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'description' => 'foo',
'billable' => true,
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -700,7 +765,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'description' => 'foo',
'billable' => false,
];
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -739,7 +806,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'description' => 'foo',
'exported' => true,
];
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], $json);
$response = $client->getResponse();
$this->assertApiResponseAccessDenied($response);
}
@@ -761,7 +830,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'end' => (new \DateTime('- 1 hours'))->format('Y-m-d H:m'),
'description' => 'foo',
];
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $timesheets[0]->getId(), 'PATCH', [], $json);
$response = $client->getResponse();
self::assertEquals(400, $response->getStatusCode());
@@ -1066,7 +1137,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'description' => 'foo',
'tags' => ['another', 'testing', 'bar']
];
$this->request($client, '/api/timesheets/' . $id, 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $id, 'PATCH', [], $json);
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH');
$this->assertTrue($client->getResponse()->isSuccessful());
@@ -1100,7 +1173,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'description' => 'foo',
'tags' => ['another', 'testing', 'bar']
];
$this->request($client, '/api/timesheets/' . $id, 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $id, 'PATCH', [], $json);
$begin = new \DateTime('2019-11-27 13:55:00');
$this->request($client, '/api/timesheets/' . $id . '/restart', 'PATCH', ['begin' => $begin->format(BaseApiController::DATE_FORMAT_PHP)]);
@@ -1211,7 +1286,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'fixedRate' => 2016,
'hourlyRate' => 127
];
$this->request($client, '/api/timesheets', 'POST', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets', 'POST', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());
$content = $client->getResponse()->getContent();
@@ -1343,7 +1420,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
'name' => 'metatestmock',
'value' => 'another,testing,bar'
];
$this->request($client, '/api/timesheets/' . $id . '/meta', 'PATCH', [], json_encode($data));
$json = json_encode($data);
self::assertIsString($json);
$this->request($client, '/api/timesheets/' . $id . '/meta', 'PATCH', [], $json);
$this->assertTrue($client->getResponse()->isSuccessful());

View File

@@ -28,7 +28,7 @@ class TagArrayToStringTransformerTest extends TestCase
$repository = $this->getMockBuilder(TagRepository::class)->disableOriginalConstructor()->getMock();
$sut = new TagArrayToStringTransformer($repository);
$sut = new TagArrayToStringTransformer($repository, true);
$this->assertEquals('', $sut->transform([]));
$this->assertEquals('', $sut->transform(null));
@@ -45,10 +45,12 @@ class TagArrayToStringTransformerTest extends TestCase
(new Tag())->setName('bar'),
];
$repository = $this->getMockBuilder(TagRepository::class)->onlyMethods(['findBy'])->disableOriginalConstructor()->getMock();
$repository = $this->getMockBuilder(TagRepository::class)
->onlyMethods(['findBy', 'saveTag'])
->disableOriginalConstructor()->getMock();
$repository->expects($this->once())->method('findBy')->willReturn($results);
$sut = new TagArrayToStringTransformer($repository);
$sut = new TagArrayToStringTransformer($repository, true);
$this->assertEquals([], $sut->reverseTransform(''));
$this->assertEquals([], $sut->reverseTransform(null));

View File

@@ -34,7 +34,9 @@ class InvoiceModelDefaultHydratorTest extends TestCase
{
$keys = [
'invoice.due_date',
'invoice.due_date_process',
'invoice.date',
'invoice.date_process',
'invoice.number',
'invoice.currency',
'invoice.currency_symbol',
@@ -47,7 +49,9 @@ class InvoiceModelDefaultHydratorTest extends TestCase
'invoice.total_time',
'invoice.duration_decimal',
'invoice.first',
'invoice.first_process',
'invoice.last',
'invoice.last_process',
'invoice.total',
'invoice.total_nc',
'invoice.total_plain',
@@ -68,11 +72,13 @@ class InvoiceModelDefaultHydratorTest extends TestCase
'query.month_number',
'query.year',
'query.begin',
'query.begin_process',
'query.begin_day',
'query.begin_month',
'query.begin_month_number',
'query.begin_year',
'query.end',
'query.end_process',
'query.end_day',
'query.end_month',
'query.end_month_number',

View File

@@ -79,7 +79,9 @@ class DebugRendererTest extends TestCase
{
$keys = [
'invoice.due_date',
'invoice.due_date_process',
'invoice.date',
'invoice.date_process',
'invoice.number',
'invoice.currency',
'invoice.currency_symbol',
@@ -92,7 +94,9 @@ class DebugRendererTest extends TestCase
'invoice.total_time',
'invoice.duration_decimal',
'invoice.first',
'invoice.first_process',
'invoice.last',
'invoice.last_process',
'invoice.total',
'invoice.total_nc',
'invoice.total_plain',
@@ -113,11 +117,13 @@ class DebugRendererTest extends TestCase
'query.month_number',
'query.year',
'query.begin',
'query.begin_process',
'query.begin_day',
'query.begin_month',
'query.begin_month_number',
'query.begin_year',
'query.end',
'query.end_process',
'query.end_day',
'query.end_month',
'query.end_month_number',

View File

@@ -38,10 +38,10 @@ class ExportQueryTest extends TimesheetQueryTest
public function assertMarkAsExported(ExportQuery $sut): void
{
$this->assertTrue($sut->isMarkAsExported());
$sut->setMarkAsExported(false);
$this->assertFalse($sut->isMarkAsExported());
$sut->setMarkAsExported(true);
$this->assertTrue($sut->isMarkAsExported());
}
public function assertRenderer(ExportQuery $sut): void

View File

@@ -17,6 +17,7 @@ use App\Saml\SamlProvider;
use App\Tests\Configuration\TestConfigLoader;
use App\Tests\Mocks\SystemConfigurationFactory;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
@@ -57,7 +58,7 @@ class SamlProviderTest extends TestCase
$userProvider->method('loadUserByIdentifier')->willReturn(new User());
}
$provider = new SamlProvider($repository, $userProvider, $samlConfig);
$provider = new SamlProvider($repository, $userProvider, $samlConfig, $this->createMock(LoggerInterface::class));
return $provider;
}
@@ -107,7 +108,7 @@ class SamlProviderTest extends TestCase
public function testAuthenticateThrowsAuthenticationException(): void
{
$this->expectException(AuthenticationException::class);
$this->expectExceptionMessage('Failed creating or hydrating user "foo1@example.com": Missing user attribute: Email');
$this->expectExceptionMessage('Failed creating or hydrating user "foo1@example.com": Missing SAML attribute in response: Email');
$user = new User();
$user->setAuth(User::AUTH_SAML);

View File

@@ -502,11 +502,6 @@ parameters:
count: 6
path: API/TimesheetControllerTest.php
-
message: "#^Parameter \\#5 \\$content of method App\\\\Tests\\\\API\\\\APIControllerBaseTest\\:\\:request\\(\\) expects string\\|null, string\\|false given\\.$#"
count: 14
path: API/TimesheetControllerTest.php
-
message: "#^Cannot access offset 'enabled' on mixed\\.$#"
count: 1