release 1.17.2 (#3135)

* allow to input password interactively in console
* added block to simplify overwriting export template parts
* prevent installation in PHP 8.1
* composer update
* phpunit version to 9
* support negative amounts in excel export
* added system configuration actions for calendar, weekly timesheet, users
* added method to render text with full markdown support
This commit is contained in:
Kevin Papst
2022-02-14 17:33:52 +01:00
committed by GitHub
parent 4cb0ac37a6
commit 83a894823f
37 changed files with 1298 additions and 892 deletions

View File

@@ -20,6 +20,7 @@ use Symfony\Component\Console\Tester\CommandTester;
/**
* @covers \App\Command\ChangePasswordCommand
* @covers \App\Command\AbstractUserCommand
* @group integration
*/
class ChangePasswordCommandTest extends KernelTestCase
@@ -59,6 +60,7 @@ class ChangePasswordCommandTest extends KernelTestCase
$input = [
'command' => $command->getName(),
];
$interactive = false;
if ($username !== null) {
$input['username'] = $username;
@@ -66,10 +68,19 @@ class ChangePasswordCommandTest extends KernelTestCase
if ($password !== null) {
$input['password'] = $password;
} else {
$interactive = true;
}
$commandTester = new CommandTester($command);
$commandTester->execute($input);
$options = [];
if ($interactive) {
$options = ['interactive' => true];
$commandTester->setInputs(['12345678']);
}
$commandTester->execute($input, $options);
return $commandTester;
}
@@ -100,11 +111,10 @@ class ChangePasswordCommandTest extends KernelTestCase
$this->callCommand(null, '1234567890');
}
public function testWithMissingPassword()
public function testWithMissingPasswordAsksForPassword()
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Not enough arguments (missing: "password").');
$this->callCommand('1234567890', null);
$commandTester = $this->callCommand('john_user', null);
$output = $commandTester->getDisplay();
$this->assertStringContainsString('[OK] Changed password for user "john_user".', $output);
}
}

View File

@@ -16,7 +16,6 @@ use App\Tests\Configuration\TestConfigLoader;
use App\Tests\Mocks\Saml\SamlAuthFactoryFactory;
use OneLogin\Saml2\Auth;
use PHPUnit\Framework\TestCase;
use PHPUnit\Util\Xml;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
@@ -83,7 +82,7 @@ class SamlControllerTest extends TestCase
public function testMetadataAction()
{
$expected = <<<EOD
$expectedXmlString = <<<EOD
<?xml version="1.0"?>
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2020-07-23T10:26:50Z" cacheDuration="PT604800S" entityID="https://127.0.0.1:8010/auth/saml/metadata">
<md:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
@@ -118,8 +117,13 @@ EOD;
self::assertInstanceOf(Response::class, $result);
self::assertEquals('xml', $result->headers->get('Content-Type'));
$expected = Xml::load($expected);
$actual = Xml::load($result->getContent());
$expected = new \DOMDocument();
$tmp = $expected->loadXML($expectedXmlString);
self::assertTrue($tmp);
$actual = new \DOMDocument();
$tmp = $actual->loadXML($result->getContent());
self::assertTrue($tmp);
// the "validUntil" attribute in the outer node changes per request
self::assertEquals($expected->firstChild->firstChild, $actual->firstChild->firstChild);

View File

@@ -10,6 +10,7 @@
namespace App\Tests\Controller;
use App\Configuration\SystemConfiguration;
use App\Entity\User;
use App\Tests\Configuration\TestConfigLoader;
use App\Tests\DataFixtures\TimesheetFixtures;
@@ -33,6 +34,11 @@ class CalendarControllerTest extends ControllerBaseTest
$this->request($client, '/calendar/');
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertPageActions($client, [
'create modal-ajax-form' => $this->createUrl('/timesheet/create'),
'help' => 'https://www.kimai.org/documentation/calendar.html'
]);
$crawler = $client->getCrawler();
$calendar = $crawler->filter('div#timesheet_calendar');
$this->assertEquals(1, $calendar->count());
@@ -40,6 +46,18 @@ class CalendarControllerTest extends ControllerBaseTest
$this->assertEquals(1, $dragAndDropBoxes->count());
}
public function testCalendarActionAsSuperAdmin()
{
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
$this->assertAccessIsGranted($client, '/calendar/');
$this->assertPageActions($client, [
'create modal-ajax-form' => $this->createUrl('/timesheet/create'),
'settings modal-ajax-form' => $this->createUrl('/admin/system-config/edit/calendar'),
'help' => 'https://www.kimai.org/documentation/calendar.html'
]);
}
public function testCalendarActionWithGoogleSource()
{
$loader = new TestConfigLoader([]);

View File

@@ -9,6 +9,7 @@
namespace App\Tests\Controller;
use App\Configuration\SystemConfiguration;
use App\Entity\Customer;
use App\Entity\CustomerComment;
use App\Entity\CustomerMeta;
@@ -161,6 +162,13 @@ class CustomerControllerTest extends ControllerBaseTest
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/1/details'));
$client->followRedirect();
$node = $client->getCrawler()->filter('div.box#comments_box .direct-chat-text');
self::assertStringContainsString('A beautiful and short comment **with some** markdown formatting', $node->html());
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$configService = static::$kernel->getContainer()->get(SystemConfiguration::class);
$configService->offsetSet('timesheet.markdown_content', true);
$this->assertAccessIsGranted($client, '/admin/customer/1/details');
$node = $client->getCrawler()->filter('div.box#comments_box .direct-chat-text');
self::assertStringContainsString('<p>A beautiful and short comment <strong>with some</strong> markdown formatting</p>', $node->html());
}

View File

@@ -9,6 +9,7 @@
namespace App\Tests\Controller;
use App\Configuration\SystemConfiguration;
use App\Entity\Activity;
use App\Entity\ActivityMeta;
use App\Entity\ActivityRate;
@@ -260,6 +261,13 @@ class ProjectControllerTest extends ControllerBaseTest
$this->assertIsRedirect($client, $this->createUrl('/admin/project/1/details'));
$client->followRedirect();
$node = $client->getCrawler()->filter('div.box#comments_box .direct-chat-text');
self::assertStringContainsString('A beautiful and long comment **with some** markdown formatting', $node->html());
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
$configService = static::$kernel->getContainer()->get(SystemConfiguration::class);
$configService->offsetSet('timesheet.markdown_content', true);
$this->assertAccessIsGranted($client, '/admin/project/1/details');
$node = $client->getCrawler()->filter('div.box#comments_box .direct-chat-text');
self::assertStringContainsString('<p>A beautiful and long comment <strong>with some</strong> markdown formatting</p>', $node->html());
}

View File

@@ -39,6 +39,7 @@ class UserControllerTest extends ControllerBaseTest
'visibility' => '#',
'download toolbar-action' => $this->createUrl('/admin/user/export'),
'create' => $this->createUrl('/admin/user/create'),
'settings modal-ajax-form' => $this->createUrl('/admin/system-config/edit/user'),
'help' => 'https://www.kimai.org/documentation/users.html'
]);
}

View File

@@ -10,7 +10,6 @@
namespace App\Tests\DependencyInjection;
use App\DependencyInjection\AppExtension;
use PHPUnit\Framework\Error\Notice;
use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -301,7 +300,7 @@ class AppExtensionTest extends TestCase
*/
public function testDurationOnlyDeprecationIsTriggered()
{
$this->expectException(Notice::class);
$this->expectNotice();
$this->expectExceptionMessage('Found ambiguous configuration: remove "kimai.timesheet.duration_only" and set "kimai.timesheet.mode" instead.');
$minConfig = $this->getMinConfig();
@@ -449,7 +448,7 @@ class AppExtensionTest extends TestCase
public function testWithBundleConfigurationFailsOnDuplicatedKey()
{
$this->expectException(Notice::class);
$this->expectNotice();
$this->expectExceptionMessage('Invalid bundle configuration "timesheet" found, skipping');
$bundleConfig = [
@@ -463,7 +462,7 @@ class AppExtensionTest extends TestCase
public function testWithBundleConfigurationFailsOnNonArray()
{
$this->expectException(Notice::class);
$this->expectNotice();
$this->expectExceptionMessage('Invalid bundle configuration found, skipping all bundle configuration');
$container = $this->getContainer();

View File

@@ -163,7 +163,18 @@ abstract class AbstractRendererTest extends KernelTestCase
->setMetaField((new TimesheetMeta())->setName('foo2')->setValue('meta-bar2')->setIsVisible(true))
;
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5];
$timesheet6 = new Timesheet();
$timesheet6
->setDuration(400)
->setFixedRate(-100.92)
->setUser((new User())->setUsername('nivek'))
->setActivity($activity)
->setProject($project)
->setBegin(new \DateTime('2019-06-16 12:00:00'))
->setEnd(new \DateTime('2019-06-16 12:06:40'))
;
$entries = [$timesheet, $timesheet2, $timesheet3, $timesheet4, $timesheet5, $timesheet6];
$query = new TimesheetQuery();
$query->setActivities([$activity]);

View File

@@ -34,7 +34,7 @@ class CsvRendererTest extends AbstractRendererTest
public function getTestModel()
{
return [
['400', '2437.12', ' EUR 1,947.99 ', 6, 5, 1, 2, 2]
['400', '2437.12', ' EUR 1,947.99 ', 7, 6, 1, 2, 2]
];
}
@@ -107,8 +107,40 @@ class CsvRendererTest extends AbstractRendererTest
27 => 'ORDER-123',
];
self::assertEquals(6, \count($all));
$expected2 = [
0 => '2019-06-16',
1 => '12:00',
2 => '12:06',
3 => '400',
4 => '0',
5 => '',
6 => 'nivek',
7 => 'nivek',
8 => 'Customer Name',
9 => 'project name',
10 => 'activity description',
11 => '',
12 => '',
13 => '',
14 => '',
15 => '',
16 => ' EUR -100.92',
17 => '',
18 => '',
19 => 'customer-bar',
20 => '',
21 => 'project-foo2',
22 => 'activity-bar',
23 => 'timesheet',
24 => 'work',
25 => 'A-0123456789',
26 => 'DE-9876543210',
27 => 'ORDER-123',
];
self::assertEquals(7, \count($all));
self::assertEquals($expected, $all[5]);
self::assertEquals($expected2, $all[6]);
self::assertEquals(\count($expected), \count($all[0]));
self::assertEquals('foo', $all[4][14]);
}

View File

@@ -82,11 +82,12 @@ class HtmlRendererTest extends AbstractRendererTest
$this->assertStringContainsString('<td>Customer Name</td>', $content);
$this->assertStringContainsString('<td>project name</td>', $content);
$this->assertStringContainsString('<span class="duration-format" data-duration="6600">01:50 h</span>', $content);
$this->assertStringContainsString('<span class="duration-format" data-duration="7000">01:56 h</span>', $content);
$this->assertStringContainsString('<td class="cost summary-rate">€2,437.12</td>', $content);
$this->assertStringContainsString('-€100.92', $content);
// 5 times in the "full list" and once in the "summary with activities"
$this->assertEquals(6, substr_count($content, 'activity description'));
$this->assertEquals(7, substr_count($content, 'activity description'));
$this->assertEquals(1, substr_count($content, '<td>activity description</td>'));
}
}

View File

@@ -26,7 +26,7 @@ class MarkdownExtensionTest extends TestCase
$config = new SystemConfiguration($loader, ['timesheet' => ['markdown_content' => true]]);
$sut = new MarkdownExtension(new Markdown(), $config);
$this->assertEquals('<p><em>test</em></p>', $sut->markdownToHtml('*test*'));
$this->assertEquals('<p># foobar</p>', $sut->markdownToHtml('# foobar'));
$this->assertEquals('<h1>foobar</h1>', $sut->markdownToHtml('# foobar'));
$this->assertEquals(
'<p><a href="javascript%3Aalert(`XSS`)">XSS</a></p>',
$sut->markdownToHtml('[XSS](javascript:alert(`XSS`))')
@@ -44,6 +44,9 @@ class MarkdownExtensionTest extends TestCase
);
$this->assertEquals('', $sut->timesheetContent(null));
$this->assertEquals('', $sut->timesheetContent(''));
$this->assertEquals('# foobar', $sut->timesheetContent('# foobar'));
$this->assertEquals('## foobar', $sut->timesheetContent('## foobar'));
$this->assertEquals('### foobar', $sut->timesheetContent('### foobar'));
$config = new SystemConfiguration($loader, ['timesheet' => ['markdown_content' => true]]);
$sut = new MarkdownExtension(new Markdown(), $config);
@@ -75,6 +78,9 @@ class MarkdownExtensionTest extends TestCase
$this->assertEquals('', $sut->commentContent(null));
$this->assertEquals('', $sut->commentContent(''));
$this->assertEquals('# foobar', $sut->commentContent('# foobar'));
$this->assertEquals('## foobar', $sut->commentContent('## foobar'));
$this->assertEquals('### foobar', $sut->commentContent('### foobar'));
$this->assertEquals('<p>' . $loremIpsum . '</p>', $sut->commentContent($loremIpsum, true));
$this->assertEquals('Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l &hellip;', $sut->commentContent($loremIpsum));

View File

@@ -13,7 +13,11 @@ use Symfony\Component\Dotenv\Dotenv;
require __DIR__ . '/../vendor/autoload.php';
(new Dotenv(false))->loadEnv(dirname(__DIR__) . '/.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$env = $_SERVER['APP_ENV'] ?? 'prod';
$debug = (bool) ($_SERVER['APP_DEBUG'] ?? (in_array($env, ['dev', 'test'])));
$kernel = new Kernel($env, $debug);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();