diff --git a/.env b/.env index fb05b5f..53ea551 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ ###> mediashare/time-tracking ### -APP_VERSION=0.4.0 +APP_VERSION=0.4.2 ###< mediashare/time-tracking ### ###> symfony/framework-bundle ### diff --git a/.env.local.php b/.env.local.php index d377ce9..87f4360 100644 --- a/.env.local.php +++ b/.env.local.php @@ -1,9 +1,9 @@ 'prod', - 'APP_VERSION' => '0.4.0', + 'APP_ENV' => 'dev', + 'APP_VERSION' => '0.4.2', 'APP_SECRET' => '91739cd3f0af5ce014a7db0d451160e2805aa31b', ); diff --git a/.env.test b/.env.test new file mode 100644 index 0000000..9e7162f --- /dev/null +++ b/.env.test @@ -0,0 +1,6 @@ +# define your env variables for the test env here +KERNEL_CLASS='App\Kernel' +APP_SECRET='$ecretf0rt3st' +SYMFONY_DEPRECATIONS_HELPER=999999 +PANTHER_APP_ENV=panther +PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots diff --git a/.gitignore b/.gitignore index 287225f..c67b3e5 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,13 @@ symfony.lock .vscode .idea ###< mediashare/time-tracking ### + +###> symfony/phpunit-bridge ### +.phpunit.result.cache +/phpunit.xml +###< symfony/phpunit-bridge ### + +###> phpunit/phpunit ### +/phpunit.xml +.phpunit.result.cache +###< phpunit/phpunit ### diff --git a/bin/phpunit b/bin/phpunit new file mode 100644 index 0000000..692bacc --- /dev/null +++ b/bin/phpunit @@ -0,0 +1,23 @@ +#!/usr/bin/env php += 80000) { + require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; + } else { + define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); + require PHPUNIT_COMPOSER_INSTALL; + PHPUnit\TextUI\Command::main(); + } +} else { + if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { + echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; + exit(1); + } + + require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; +} diff --git a/composer.json b/composer.json index 00d8a00..5bbe6be 100644 --- a/composer.json +++ b/composer.json @@ -92,5 +92,11 @@ "time-tracking": { "console": "bin/time-tracking" } + }, + "require-dev": { + "phpunit/phpunit": "^9.5", + "symfony/browser-kit": "^6.4", + "symfony/css-selector": "^6.4", + "symfony/phpunit-bridge": "^7.0" } } diff --git a/config/bootstrap.php b/config/bootstrap.php deleted file mode 100644 index 75bdee0..0000000 --- a/config/bootstrap.php +++ /dev/null @@ -1,23 +0,0 @@ -=1.2) -if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV']) { - foreach ($env as $k => $v) { - $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v); - } -} elseif (!class_exists(Dotenv::class)) { - throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.'); -} else { - // load all the .env files - (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env.local.php'); -} - -$_SERVER += $_ENV; -$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; -$_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; -$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..c76a655 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + tests + + + + + + src + + + + + + + + + + diff --git a/src/Command/ArchiveCommand.php b/src/Command/ArchiveCommand.php index 35ff172..72ed0b9 100644 --- a/src/Command/ArchiveCommand.php +++ b/src/Command/ArchiveCommand.php @@ -42,8 +42,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { ); // Tracking - $trackingService = new TrackingService($config); - $tracking = $trackingService->archiveTracking($input->getOption('stop')); + $trackingService = new TrackingService($config, createItIfNotExist: !$input->getOption('stop')); + $tracking = $trackingService->archiveTracking(); // Update tracking data file $serializerService = new SerializerService(); @@ -66,9 +66,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { $input->getOption('config-datetime-format'), $lastTrackingDirectory, $config->getTrackingId() === $lastTrackingId - ? (new \DateTime())->format( - $input->getOption('config-datetime-format') ?? $configService->getLastDateTimeFormat() - ) : $lastTrackingId + ? (new \DateTime())->format('YmdHis') + : $lastTrackingId , ); diff --git a/src/Command/RemoveCommand.php b/src/Command/RemoveCommand.php index 35b484f..09e6458 100644 --- a/src/Command/RemoveCommand.php +++ b/src/Command/RemoveCommand.php @@ -40,7 +40,7 @@ protected function execute(InputInterface $input, OutputInterface $output) { ); // Tracking - $trackingService = new TrackingService($config); + $trackingService = new TrackingService($config, createItIfNotExist: false); $tracking = $trackingService->getTracking(createItIfNotExist: false); $trackingService->removeTracking(); @@ -61,9 +61,8 @@ protected function execute(InputInterface $input, OutputInterface $output) { $input->getOption('config-datetime-format'), $lastTrackingDirectory, $config->getTrackingId() === $lastTrackingId - ? (new \DateTime())->format( - $input->getOption('config-datetime-format') ?? $configService->getLastDateTimeFormat() - ) : $lastTrackingId + ? (new \DateTime())->format('YmdHis') + : $lastTrackingId , ); diff --git a/src/Entity/Commit.php b/src/Entity/Commit.php index f252bf8..b35c1d5 100644 --- a/src/Entity/Commit.php +++ b/src/Entity/Commit.php @@ -13,7 +13,7 @@ class Commit { private string|null $id = null; private string $message = ''; - private string $duration = '00:00:00'; + private StepCollection $steps; public function __construct() { diff --git a/src/Entity/Tracking.php b/src/Entity/Tracking.php index 5c9da09..407a3d9 100644 --- a/src/Entity/Tracking.php +++ b/src/Entity/Tracking.php @@ -35,7 +35,7 @@ public function setId(string $id): self { return $this; } - public function getId(): string { + public function getId(): string|null { return $this->id; } diff --git a/src/Service/CommitService.php b/src/Service/CommitService.php index 2f6fbb9..72bc97a 100644 --- a/src/Service/CommitService.php +++ b/src/Service/CommitService.php @@ -28,6 +28,9 @@ public function createCommit(?string $message = null, ?string $duration = null): $commit->addStep( $this->stepService->createStepWithCustomDuration( $duration, + ($lastStep = $this->tracking->getSteps()?->last())?->getEndDate() + ? $lastStep->getStartDate() + : null ) ); else: @@ -55,6 +58,9 @@ public function createCommit(?string $message = null, ?string $duration = null): return $this->tracking; } + /** + * @throws CommitNotFoundException + */ public function editCommit( string $id, string|false $message = false, @@ -102,6 +108,9 @@ public function editCommit( return $this->tracking; } + /** + * @throws CommitNotFoundException + */ public function removeCommit( string $id, ): Tracking { diff --git a/src/Service/ConfigService.php b/src/Service/ConfigService.php index 4eae300..6fefa31 100644 --- a/src/Service/ConfigService.php +++ b/src/Service/ConfigService.php @@ -53,9 +53,19 @@ public function getLastTrackingDirectory(): string { } public function getLastTrackingId(string $trackingDirectory): string|null { - return $this->getLastConfig()->getTrackingId() - ?? (new TrackingService(new Config(trackingDirectory: $trackingDirectory)))->getTrackings()?->last()?->getId() - ; + try { + if ($lastTrackingIdByConfig = $this->getLastConfig()->getTrackingId()): + return $lastTrackingIdByConfig; + endif; + + return (new TrackingService(new Config(trackingDirectory: $trackingDirectory))) + ->getTrackings()?->last()?->getId(); + + } catch (\Exception $exception) { + + } + + return null; } private function getLastConfig(): Config { diff --git a/src/Service/SerializerService.php b/src/Service/SerializerService.php index 47118dd..a3d90af 100644 --- a/src/Service/SerializerService.php +++ b/src/Service/SerializerService.php @@ -29,7 +29,9 @@ public function __construct() { } /** - * Convert json file to Tracking Object + * Convert json file to Entity object + * @throws FileNotFoundException + * @throws JsonDecodeException */ public function read(string $filepath, string $className): Tracking|Config { if (!$this->filesystem->exists($filepath)): diff --git a/src/Service/TrackingService.php b/src/Service/TrackingService.php index 168aa56..7255a38 100644 --- a/src/Service/TrackingService.php +++ b/src/Service/TrackingService.php @@ -12,12 +12,19 @@ class TrackingService { private SerializerService $serializerService; private Filesystem $filesystem; + private StepService $stepService; + private Tracking $tracking; public function __construct( private Config $config, + bool $createItIfNotExist = true ) { $this->serializerService = new SerializerService(); $this->filesystem = new Filesystem(); + + $this->stepService = new StepService(); + + $this->tracking = $this->getTracking($createItIfNotExist); } /** @@ -41,28 +48,24 @@ public function getTrackings(): TrackingCollection { public function getTracking(bool $createItIfNotExist = true): Tracking { $trackingExist = $this->filesystem->exists($filepath = $this->getTrackingFilepath()); if (!$trackingExist && $createItIfNotExist): - return $this->createTracking(); + return $this->tracking = $this->createTracking(); elseif (!$trackingExist): throw new TrackingNotFoundException(); endif; - return $this->serializerService->read($filepath, Tracking::class); + return $this->tracking = $this->serializerService->read($filepath, Tracking::class); } public function createTracking(array $data = []): Tracking { - if (!array_keys($data, 'id')): - $data = array_merge($data, [ - 'id' => $this->config->getTrackingId() - ?? (new \DateTime())->format('YmdHis') - ]); - endif; - /** @var Tracking $tracking */ $tracking = $this->serializerService->arrayToEntity($data, Tracking::class); + if (!$tracking->getId()): + $tracking->setId($this->config->getTrackingId() ?? (new \DateTime())->format('YmdHis')); + endif; + if ($tracking->isRun() && !$tracking->getSteps()?->last()?->getEndDate()): - $stepService = new StepService($tracking); - $tracking->addStep($stepService->createStep()); + $tracking->addStep($this->stepService->createStep()); endif; $this->serializerService->writeTracking($this->getTrackingFilepath(), $tracking); @@ -72,27 +75,36 @@ public function createTracking(array $data = []): Tracking { public function startTracking( string|false $name = false, - string|null $duration = null, + string|false $duration = false, ): Tracking { - $tracking = $this->getTracking()->setRun(true); + $tracking = $this->tracking->setRun(true); $tracking->setName($name !== false ? $name : $tracking->getName()); + if ($duration): + $firstStep = $tracking->getSteps()->first(); + $tracking->getSteps()->clear(); + $tracking->addStep($this + ->stepService + ->createStepWithCustomDuration( + $duration, + $firstStep?->getStartDate(), + ) + ); + endif; + if (!$tracking->getStartDate() || !($lastStep = $tracking->getSteps()?->last()) || $lastStep->getEndDate()): - $stepService = new StepService(); $tracking ->addStep( - $duration - ? $stepService->createStepWithCustomDuration($duration) - : $stepService->createStep() + $this->stepService->createStep() ); endif; return $tracking; } - public function stopTracking(bool $createItIfNotExist = true): Tracking { - $tracking = $this->getTracking($createItIfNotExist); - $tracking->setRun(false); + public function stopTracking(): Tracking { + $tracking = $this->tracking + ->setRun(false); if (($lastStep = $tracking->getSteps()?->last()) && !$lastStep->getEndDate()): $tracking @@ -106,10 +118,9 @@ public function stopTracking(bool $createItIfNotExist = true): Tracking { return $tracking; } - public function archiveTracking(bool $stop = false): Tracking { - $stop ? $this->stopTracking(createItIfNotExist: false) : null; + public function archiveTracking(): Tracking { return $this - ->getTracking() + ->stopTracking() ->setArchived(true); } @@ -121,6 +132,9 @@ public function removeTracking(): self { return $this; } + /** + * @throws TrackingNotFoundException + */ public function getTrackingFilepath(): string { if (!$this->config->getTrackingId()): throw new TrackingNotFoundException(); diff --git a/src/Trait/EntityDurationTrait.php b/src/Trait/EntityDurationTrait.php index c1c334b..4714e2e 100644 --- a/src/Trait/EntityDurationTrait.php +++ b/src/Trait/EntityDurationTrait.php @@ -7,19 +7,19 @@ use Mediashare\TimeTracking\Entity\Tracking; trait EntityDurationTrait { - private string $duration = '00:00:00'; - /** * Convert seconds to "d H:i:s" format */ public function getDuration(bool|null $onlyNotCommited = false, int $totalSeconds = 0): string { $seconds = $this->getSeconds($onlyNotCommited) + $totalSeconds; - return $this->duration = sprintf( - '%s %02d:%02d:%02d', - ((($seconds/86400%60) !== 0) ? ($seconds/86400%60) . 'd' : ''), - ($seconds/3600%24), - ($seconds/60%60), - $seconds%60 + return $this->duration = trim( + sprintf( + '%s %02d:%02d:%02d', + ((($seconds/86400%60) !== 0) ? ($seconds/86400%60) . 'd' : ''), + ($seconds/3600%24), + ($seconds/60%60), + $seconds%60 + ) ); } diff --git a/tests/AbstractTestCase.php b/tests/AbstractTestCase.php new file mode 100644 index 0000000..39fcd4a --- /dev/null +++ b/tests/AbstractTestCase.php @@ -0,0 +1,12 @@ +format('YmdHis') + ); + + $this->commitService = new CommitService($config); + } + + public function testCreateCommit(): void { + $tracking = $this->commitService->createCommit('Test Commit', '+2 hours'); + + $this->assertInstanceOf(Tracking::class, $tracking); + $this->assertCount(1, $commits = $tracking->getCommits()); + + $this->assertInstanceOf(CommitCollection::class, $commits); + $this->assertInstanceOf(Commit::class, $commit = $commits->first()); + + $this->assertInstanceOf(StepCollection::class, $steps = $commit->getSteps()); + $this->assertInstanceOf(Step::class, $lastStep = $steps->first()); + $this->assertEquals("02:00:00", $lastStep->getDuration()); + + $this->assertCount(1, $steps = $tracking->getSteps()); + $this->assertInstanceOf(StepCollection::class, $steps); + $this->assertInstanceOf(Step::class, $steps->first()); + } + + public function testEditCommit(): void { + $tracking = $this->commitService->createCommit('Original Commit', '+1 hour'); + $originalCommitId = $tracking->getCommits()->first()->getId(); + + $tracking = $this->commitService->editCommit($originalCommitId, 'Updated Commit', '+30 minutes'); + + $this->assertInstanceOf(Tracking::class, $tracking); + $this->assertCount(1, $tracking->getCommits()); + $this->assertCount(1, $tracking->getSteps()); + + $editedCommit = $tracking->getCommits()->first(); + $this->assertEquals('Updated Commit', $editedCommit->getMessage()); + } + + public function testRemoveCommit(): void { + $tracking = $this->commitService->createCommit('To Be Removed Commit', '+3 hours'); + $toBeRemovedCommitId = $tracking->getCommits()->first()->getId(); + + $tracking = $this->commitService->removeCommit($toBeRemovedCommitId); + + $this->assertInstanceOf(Tracking::class, $tracking); + $this->assertCount(0, $tracking->getCommits()); + $this->assertCount(1, $tracking->getSteps()); + } + + public function testRemoveNonexistentCommit(): void { + $this->expectException(CommitNotFoundException::class); + + $this->commitService->removeCommit('NonexistentCommitId'); + } +} \ No newline at end of file diff --git a/tests/Service/ConfigServiceTest.php b/tests/Service/ConfigServiceTest.php new file mode 100644 index 0000000..db9a7e7 --- /dev/null +++ b/tests/Service/ConfigServiceTest.php @@ -0,0 +1,61 @@ +tempConfigPath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'time-tracking' . DIRECTORY_SEPARATOR . 'temp_config.json'; + $this->configService = new ConfigService(); + } + + protected function tearDown(): void + { + if (file_exists($this->tempConfigPath)) { + unlink($this->tempConfigPath); + } + } + + public function testCreateConfig() + { + $config = $this->configService->createConfig($this->tempConfigPath); + + $this->assertFileExists($this->tempConfigPath); + $this->assertInstanceOf(Config::class, $config); + $this->assertEquals(Config::DATETIME_FORMAT, $config->getDateTimeFormat()); + } + + public function testGetLastDateTimeFormat() + { + // Create a config file with a specific datetime format + $config = $this->configService->createConfig($this->tempConfigPath, 'Y-m-d H:i:s'); + $lastDateTimeFormat = $this->configService->getLastDateTimeFormat(); + + $this->assertEquals('Y-m-d H:i:s', $lastDateTimeFormat); + } + + public function testGetLastTrackingDirectory() + { + // Create a config file with a specific tracking directory + $config = $this->configService->createConfig($this->tempConfigPath, null, '/path/to/tracking'); + $lastTrackingDirectory = $this->configService->getLastTrackingDirectory(); + + $this->assertEquals('/path/to/tracking', $lastTrackingDirectory); + } + + public function testGetLastTrackingId() + { + // Create a config file with a specific tracking directory and ID + $config = $this->configService->createConfig($this->tempConfigPath, null, '/path/to/tracking', '12345'); + $lastTrackingId = $this->configService->getLastTrackingId('/path/to/tracking'); + + $this->assertEquals('12345', $lastTrackingId); + } +} \ No newline at end of file diff --git a/tests/Service/StepServiceTest.php b/tests/Service/StepServiceTest.php new file mode 100644 index 0000000..3d0babb --- /dev/null +++ b/tests/Service/StepServiceTest.php @@ -0,0 +1,65 @@ +stepService = new StepService(); + } + + + public function testCreateStepWithNoDates(): void { + $step = $this->stepService->createStep(); + + $this->assertInstanceOf(Step::class, $step); + $this->assertNotNull($step->getStartDate()); + $this->assertNull($step->getEndDate()); + } + + public function testCreateStepWithCustomStartDate(): void { + $customStartDate = strtotime('2023-01-01'); + $step = $this->stepService->createStep($customStartDate); + + $this->assertInstanceOf(Step::class, $step); + $this->assertEquals($customStartDate, $step->getStartDate()); + $this->assertNull($step->getEndDate()); + } + + public function testCreateStepWithEndDate(): void { + $endDate = strtotime('2023-02-01'); + $step = $this->stepService->createStep(null, $endDate); + + $this->assertInstanceOf(Step::class, $step); + $this->assertNotNull($step->getStartDate()); + $this->assertEquals($endDate, $step->getEndDate()); + } + + public function testCreateStepWithCustomDuration(): void { + $customDuration = '+5 minutes'; + $step = $this->stepService->createStepWithCustomDuration($customDuration); + + $this->assertInstanceOf(Step::class, $step); + $this->assertNotNull($step->getStartDate()); + $this->assertNotNull($step->getEndDate()); + $this->assertGreaterThan($step->getStartDate(), $step->getEndDate()); + $this->assertEquals('00:05:00', $step->getDuration()); + } + + public function testCreateStepWithCustomDurationAndStartDate(): void { + $customDuration = '+2 hours'; + $customStartDate = strtotime('2023-03-01'); + $step = $this->stepService->createStepWithCustomDuration($customDuration, $customStartDate); + + $this->assertInstanceOf(Step::class, $step); + $this->assertEquals($customStartDate, $step->getStartDate()); + $this->assertNotNull($step->getEndDate()); + $this->assertGreaterThan($step->getStartDate(), $step->getEndDate()); + $this->assertEquals('02:00:00', $step->getDuration()); + } +} \ No newline at end of file diff --git a/tests/Service/TrackingServiceTest.php b/tests/Service/TrackingServiceTest.php new file mode 100644 index 0000000..73bc065 --- /dev/null +++ b/tests/Service/TrackingServiceTest.php @@ -0,0 +1,86 @@ +config = new Config( + trackingDirectory: sys_get_temp_dir() . DIRECTORY_SEPARATOR . 'time-tracking', + trackingId: (new \DateTime())->format('YmdHis') + ); + $this->trackingService = new TrackingService($this->config); + } + + public function testCreateTracking(): void { + $tracking = $this->trackingService->createTracking(['id' => 'test_id']); + + $this->assertInstanceOf(Tracking::class, $tracking); + $this->assertEquals('test_id', $tracking->getId()); + } + + public function testGetTracking(): void { + $tracking = $this->trackingService->getTracking(); + + $this->assertInstanceOf(Tracking::class, $tracking); + $this->assertTrue($tracking->isRun()); + } + + public function testStartTracking(): void { + $tracking = $this->trackingService->startTracking('Test Tracking', '+1 hour'); + + $this->assertTrue($tracking->isRun()); + $this->assertEquals('Test Tracking', $tracking->getName()); + $this->assertCount(2, $tracking->getSteps()); + + $step = $tracking->getSteps()->first(); + $this->assertInstanceOf(Step::class, $step); + $this->assertTrue($step->getEndDate() > $step->getStartDate()); + + $step = $tracking->getSteps()->last(); + $this->assertInstanceOf(Step::class, $step); + $this->assertNotNull($step->getStartDate()); + $this->assertNull($step->getEndDate()); + } + + public function testStopTracking(): void { + $tracking = $this->trackingService->startTracking('Test Tracking', '+1 hour'); + $tracking = $this->trackingService->stopTracking(); + + $this->assertFalse($tracking->isRun()); + $this->assertCount(2, $tracking->getSteps()); + + $step = $tracking->getSteps()->first(); + $this->assertInstanceOf(Step::class, $step); + $this->assertTrue($step->getEndDate() > $step->getStartDate()); + + $step = $tracking->getSteps()->last(); + $this->assertInstanceOf(Step::class, $step); + $this->assertSame($step->getEndDate(), $step->getStartDate()); + } + + public function testArchiveTracking(): void { + $tracking = $this->trackingService->startTracking('Test Tracking', '+1 hour'); + $tracking = $this->trackingService->archiveTracking(); + + $this->assertTrue($tracking->isArchived()); + } + + public function testRemoveTracking(): void { + $tracking = $this->trackingService->startTracking('Test Tracking', '+1 hour'); + $this->trackingService->removeTracking(); + + $this->expectException(TrackingNotFoundException::class); + $this->trackingService->getTracking(createItIfNotExist: false); + } +} \ No newline at end of file diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..3181151 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,15 @@ +bootEnv(dirname(__DIR__).'/.env'); +} + +if ($_SERVER['APP_DEBUG']) { + umask(0000); +} diff --git a/time-tracking b/time-tracking index 2f916ca..22c1212 100644 Binary files a/time-tracking and b/time-tracking differ