diff --git a/src/Cli/WorkReportCommand.php b/src/Cli/WorkReportCommand.php index d6ae7f8..9e00675 100644 --- a/src/Cli/WorkReportCommand.php +++ b/src/Cli/WorkReportCommand.php @@ -124,15 +124,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int Banner::render($output, $this->getApplication()?->getVersion() ?? 'dev'); try { - $config = $this->configProvider->get(); - } catch (ConfigException $e) { + $config = $this->configProvider->getConfig(); + $timeEntriesSource = $this->timeEntriesSourceFactory->build($config->source); + $this->destination = $this->destinationFactory->build($config->destination); + } catch (SourceException | DestinationException | ConfigException $e) { $this->io->error($e->getMessage()); return Command::FAILURE; } - $timeEntriesSource = $this->timeEntriesSourceFactory->build($config->source); - $this->destination = $this->destinationFactory->build($config->destination); - try { $timeEntries = $timeEntriesSource->fetchTimeEntries($this->from, $this->to); } catch (SourceException $e) { diff --git a/src/Config/Config.php b/src/Config/Config.php index 9ad22d1..b97eba1 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -6,6 +6,7 @@ use Igancev\WorkReporter\Config\SourceConfig\SourcesConfig; use Igancev\WorkReporter\Source\SourceType; use Igancev\WorkReporter\Destination\DestinationType; +use LogicException; readonly class Config { @@ -15,5 +16,21 @@ public function __construct( public SourcesConfig $sources, public DestinationsConfig $destinations, ) { + // @phpstan-ignore function.alreadyNarrowedType + if (!property_exists($this->destinations, $this->destination->value)) { + throw new LogicException("Destination {$this->destination->value} is not supported"); + } + + if (!property_exists($this->sources, $this->source->value)) { + throw new LogicException("Source {$this->source->value} is not supported"); + } + + if ($this->destinations->{$this->destination->value} === null) { + throw new ConfigException("Destination {$this->destination->value} is not configured"); + } + + if ($this->sources->{$this->source->value} === null) { + throw new ConfigException("Source {$this->source->value} is not configured"); + } } } diff --git a/src/Config/ConfigProvider.php b/src/Config/ConfigProvider.php index f0c5ca6..01df9f4 100644 --- a/src/Config/ConfigProvider.php +++ b/src/Config/ConfigProvider.php @@ -4,5 +4,8 @@ interface ConfigProvider { - public function get(): Config; + /** + * @throws ConfigException + */ + public function getConfig(): Config; } diff --git a/src/Config/YamlConfigProvider.php b/src/Config/YamlConfigProvider.php index bd0929f..543b2be 100644 --- a/src/Config/YamlConfigProvider.php +++ b/src/Config/YamlConfigProvider.php @@ -22,7 +22,7 @@ public function __construct( ) { } - public function get(): Config + public function getConfig(): Config { if ($this->config !== null) { return $this->config; diff --git a/src/Destination/ConcreteDestinationFactory.php b/src/Destination/ConcreteDestinationFactory.php index e28a979..661c4ab 100644 --- a/src/Destination/ConcreteDestinationFactory.php +++ b/src/Destination/ConcreteDestinationFactory.php @@ -10,7 +10,6 @@ use Amp\Socket\DnsSocketConnector; use Igancev\WorkReporter\Config\ConfigProvider; use Igancev\WorkReporter\Destination\YouTrack\YouTrackDestination; -use RuntimeException; final readonly class ConcreteDestinationFactory implements DestinationFactory { @@ -26,11 +25,14 @@ public function build(DestinationType $destination): Destination }; } + /** + * @throws DestinationException + */ private function buildYouTrackDestination(): YouTrackDestination { - $config = $this->configProvider->get()->destinations->youTrack; - if ($config === null) { - throw new RuntimeException("YouTrack destination configuration is missing"); + $youtrackConfigDestinations = $this->configProvider->getConfig()->destinations->youTrack; + if ($youtrackConfigDestinations === null) { + throw new DestinationException("Definition of YouTrack destination missing in configuration"); } return new YouTrackDestination( @@ -39,8 +41,8 @@ private function buildYouTrackDestination(): YouTrackDestination new UnlimitedConnectionPool(new DefaultConnectionFactory(new DnsSocketConnector())) ) ->build(), - $config->url, - $config->token, + $youtrackConfigDestinations->url, + $youtrackConfigDestinations->token, ); } } diff --git a/src/Destination/DestinationFactory.php b/src/Destination/DestinationFactory.php index 3ff486b..4cb393c 100644 --- a/src/Destination/DestinationFactory.php +++ b/src/Destination/DestinationFactory.php @@ -6,5 +6,8 @@ interface DestinationFactory { + /** + * @throws DestinationException + */ public function build(DestinationType $destination): Destination; } diff --git a/src/Source/ConcreteSourceFactory.php b/src/Source/ConcreteSourceFactory.php index 0e34fc1..7a75bf3 100644 --- a/src/Source/ConcreteSourceFactory.php +++ b/src/Source/ConcreteSourceFactory.php @@ -7,7 +7,6 @@ use Igancev\WorkReporter\Config\ConfigProvider; use Igancev\WorkReporter\Source\PlainJson\PlainJsonTimeEntriesSource; use Igancev\WorkReporter\Source\SuperProductivity\SuperProductivitySyncSource; -use RuntimeException; final readonly class ConcreteSourceFactory implements TimeEntriesSourceFactory { @@ -24,21 +23,27 @@ public function build(SourceType $source): TimeEntriesSource }; } + /** + * @throws SourceException + */ private function buildPlainJsonSource(): PlainJsonTimeEntriesSource { - $config = $this->configProvider->get()->sources->plainJson; + $config = $this->configProvider->getConfig()->sources->plainJson; if ($config === null) { - throw new RuntimeException("PlainJson source configuration is missing"); + throw new SourceException("PlainJson source configuration is missing"); } return new PlainJsonTimeEntriesSource($config->filePath); } + /** + * @throws SourceException + */ private function buildFromSuperProductivitySource(): SuperProductivitySyncSource { - $config = $this->configProvider->get()->sources->superProductivity; + $config = $this->configProvider->getConfig()->sources->superProductivity; if ($config === null) { - throw new RuntimeException("SuperProductivity source configuration is missing"); + throw new SourceException("SuperProductivity source configuration is missing"); } return new SuperProductivitySyncSource($config->syncFilePath); diff --git a/src/Source/SuperProductivity/Storage.php b/src/Source/SuperProductivity/Storage.php index 2420a9a..d92f6fc 100644 --- a/src/Source/SuperProductivity/Storage.php +++ b/src/Source/SuperProductivity/Storage.php @@ -4,8 +4,8 @@ namespace Igancev\WorkReporter\Source\SuperProductivity; +use Igancev\WorkReporter\Source\SourceException; use JsonException; -use RuntimeException; /** * @internal @@ -16,6 +16,9 @@ /** @var array */ private array $jsonData; + /** + * @throws SourceException + */ public function __construct(string $syncMetaPath) { $this->syncMetaPath = $syncMetaPath; @@ -48,6 +51,7 @@ public function getTaskIds(): iterable * subTaskIds: string[], * tagIds: string[], * } + * @throws SourceException */ public function getTaskById(string $taskId): array { @@ -61,16 +65,19 @@ public function getTaskById(string $taskId): array return $this->jsonData['mainModelData']['archiveOld']['task']['entities'][$taskId]; } - throw new RuntimeException('SuperProductivitySyncDataSource: Unable to find task with id ' . $taskId); + throw new SourceException('SuperProductivitySyncDataSource: Unable to find task with id ' . $taskId); } - /** @return array */ + /** + * @return array + * @throws SourceException + */ private function parseJson(): array { $content = @file_get_contents($this->syncMetaPath); if ($content === false) { - throw new RuntimeException( - 'SuperProductivitySyncDataSource: Unable to read sync data file ' . $this->syncMetaPath, + throw new SourceException( + 'SuperProductivitySyncDataSource: Unable to read sync data file: ' . $this->syncMetaPath, ); } @@ -78,7 +85,7 @@ private function parseJson(): array // json file starts with prefix like `pf_4.4__`, example: pf_4.4__{"revMap":{"menuTree":"1770462116462", ... $startPos = strpos($content, '{'); if ($startPos === false) { - throw new RuntimeException( + throw new SourceException( 'SuperProductivitySyncDataSource: Unable to parse start position "{" ' . $this->syncMetaPath, ); } @@ -88,16 +95,19 @@ private function parseJson(): array try { $jsonData = json_decode($jsonString, true, 512, JSON_THROW_ON_ERROR); } catch (JsonException $e) { - throw new RuntimeException('SuperProductivitySyncDataSource: Unable to parse JSON: ' . $e->getMessage()); + throw new SourceException('SuperProductivitySyncDataSource: Unable to parse JSON: ' . $e->getMessage()); } return $jsonData; } + /** + * @throws SourceException + */ public function getTagById(string $tagId): Tag { if (!array_key_exists($tagId, $this->jsonData['mainModelData']['tag']['entities'])) { - throw new RuntimeException('SuperProductivitySyncDataSource: Unable to find tag with id ' . $tagId); + throw new SourceException('SuperProductivitySyncDataSource: Unable to find tag with id ' . $tagId); } $tagName = $this->jsonData['mainModelData']['tag']['entities'][$tagId]['title']; diff --git a/src/Source/SuperProductivity/SuperProductivitySyncSource.php b/src/Source/SuperProductivity/SuperProductivitySyncSource.php index 7b959cf..23bbc6d 100644 --- a/src/Source/SuperProductivity/SuperProductivitySyncSource.php +++ b/src/Source/SuperProductivity/SuperProductivitySyncSource.php @@ -4,6 +4,7 @@ namespace Igancev\WorkReporter\Source\SuperProductivity; +use Igancev\WorkReporter\Source\SourceException; use Igancev\WorkReporter\Source\TimeEntriesSource; use Igancev\WorkReporter\TimeEntry; use DateTimeImmutable; @@ -13,6 +14,9 @@ private Storage $storage; private TaskFactory $taskFactory; + /** + * @throws SourceException + */ public function __construct( string $syncMetaPath, ) { diff --git a/src/Source/TimeEntriesSourceFactory.php b/src/Source/TimeEntriesSourceFactory.php index c374c71..226649e 100644 --- a/src/Source/TimeEntriesSourceFactory.php +++ b/src/Source/TimeEntriesSourceFactory.php @@ -6,5 +6,8 @@ interface TimeEntriesSourceFactory { + /** + * @throws SourceException + */ public function build(SourceType $source): TimeEntriesSource; } diff --git a/tests/Unit/Config/YamlConfigProviderTest.php b/tests/Unit/Config/YamlConfigProviderTest.php index 846f053..fc70542 100644 --- a/tests/Unit/Config/YamlConfigProviderTest.php +++ b/tests/Unit/Config/YamlConfigProviderTest.php @@ -38,7 +38,7 @@ public function testThrowsExceptionWhenConfigFileNotFound(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Configuration file not found at'); - $provider->get(); + $provider->getConfig(); } public function testParsesFullConfig(): void @@ -59,7 +59,7 @@ public function testParsesFullConfig(): void YAML); $provider = new YamlConfigProvider($configPath); - $config = $provider->get(); + $config = $provider->getConfig(); $this->assertSame(SourceType::SuperProductivity, $config->source); $this->assertSame(DestinationType::YouTrack, $config->destination); @@ -72,7 +72,7 @@ public function testParsesFullConfig(): void $this->assertSame('test-token-123', $config->destinations->youTrack->token); } - public function testParsesMinimalConfig(): void + public function testThrowsExceptionWhenDestinationNotConfigured(): void { $configPath = $this->tempDir . '/config.yaml'; file_put_contents($configPath, <<get(); + $this->expectException(ConfigException::class); + $this->expectExceptionMessage('Destination youTrack is not configured'); - $this->assertSame(SourceType::PlainJson, $config->source); - $this->assertSame(DestinationType::YouTrack, $config->destination); - $this->assertNull($config->sources->superProductivity); - $this->assertNull($config->sources->plainJson); - $this->assertNull($config->destinations->youTrack); + $provider = new YamlConfigProvider($configPath); + $provider->getConfig(); } public function testCachesConfigOnSubsequentCalls(): void @@ -98,14 +95,19 @@ public function testCachesConfigOnSubsequentCalls(): void file_put_contents($configPath, <<get(); + $first = $provider->getConfig(); file_put_contents($configPath, 'corrupted'); - $second = $provider->get(); + $second = $provider->getConfig(); $this->assertSame($first, $second); } @@ -120,7 +122,7 @@ public function testThrowsExceptionOnInvalidYamlSyntax(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Invalid YAML syntax in config file'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionOnEmptyConfigFile(): void @@ -133,7 +135,7 @@ public function testThrowsExceptionOnEmptyConfigFile(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Configuration file is empty or has invalid structure'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenSourceKeyMissing(): void @@ -150,7 +152,7 @@ public function testThrowsExceptionWhenSourceKeyMissing(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required config key: "source"'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenSourceKeyIsNotString(): void @@ -170,7 +172,7 @@ public function testThrowsExceptionWhenSourceKeyIsNotString(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Config key "source" must be a string, got array'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenSourcesKeyIsNotMapping(): void @@ -188,7 +190,7 @@ public function testThrowsExceptionWhenSourcesKeyIsNotMapping(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Config key "sources" must be a mapping, got string'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenDestinationsKeyIsNotMapping(): void @@ -206,7 +208,7 @@ public function testThrowsExceptionWhenDestinationsKeyIsNotMapping(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Config key "destinations" must be a mapping, got string'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenYouTrackConfigIsNotMapping(): void @@ -225,7 +227,7 @@ public function testThrowsExceptionWhenYouTrackConfigIsNotMapping(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Invalid youTrack destination config: expected a mapping.'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenDestinationKeyIsNotString(): void @@ -245,7 +247,7 @@ public function testThrowsExceptionWhenDestinationKeyIsNotString(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Config key "destination" must be a string, got array'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenDestinationKeyMissing(): void @@ -262,7 +264,7 @@ public function testThrowsExceptionWhenDestinationKeyMissing(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required config key: "destination"'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionOnInvalidSourceType(): void @@ -280,7 +282,7 @@ public function testThrowsExceptionOnInvalidSourceType(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Invalid source type: "unknown"'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionOnInvalidDestinationType(): void @@ -298,7 +300,7 @@ public function testThrowsExceptionOnInvalidDestinationType(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Invalid destination type: "jira"'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenYouTrackMissingUrl(): void @@ -318,7 +320,7 @@ public function testThrowsExceptionWhenYouTrackMissingUrl(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required key "url" in destinations.youTrack config'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenYouTrackMissingToken(): void @@ -338,7 +340,7 @@ public function testThrowsExceptionWhenYouTrackMissingToken(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required key "token" in destinations.youTrack config'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenPlainJsonMissingFilePath(): void @@ -358,7 +360,7 @@ public function testThrowsExceptionWhenPlainJsonMissingFilePath(): void $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required key "filePath" in sources.plainJson config'); - $provider->get(); + $provider->getConfig(); } public function testThrowsExceptionWhenSuperProductivityMissingSyncFilePath(): void @@ -378,7 +380,7 @@ public function testThrowsExceptionWhenSuperProductivityMissingSyncFilePath(): v $this->expectException(ConfigException::class); $this->expectExceptionMessage('Missing required key "syncFilePath" in sources.superProductivity config'); - $provider->get(); + $provider->getConfig(); } public function testParsesConfigWithOnlySuperProductivitySource(): void @@ -397,7 +399,7 @@ public function testParsesConfigWithOnlySuperProductivitySource(): void YAML); $provider = new YamlConfigProvider($configPath); - $config = $provider->get(); + $config = $provider->getConfig(); $this->assertNotNull($config->sources->superProductivity); $this->assertNull($config->sources->plainJson); @@ -420,7 +422,7 @@ public function testParsesConfigWithOnlyPlainJsonSource(): void YAML); $provider = new YamlConfigProvider($configPath); - $config = $provider->get(); + $config = $provider->getConfig(); $this->assertNull($config->sources->superProductivity); $this->assertNotNull($config->sources->plainJson); diff --git a/tests/Unit/Destination/ConcreteDestinationFactoryTest.php b/tests/Unit/Destination/ConcreteDestinationFactoryTest.php index 1c25540..6360274 100644 --- a/tests/Unit/Destination/ConcreteDestinationFactoryTest.php +++ b/tests/Unit/Destination/ConcreteDestinationFactoryTest.php @@ -8,6 +8,7 @@ use Igancev\WorkReporter\Config\ConfigProvider; use Igancev\WorkReporter\Config\DestinationConfig\DestinationsConfig; use Igancev\WorkReporter\Config\DestinationConfig\YouTrackConfig; +use Igancev\WorkReporter\Config\SourceConfig\PlainJsonConfig; use Igancev\WorkReporter\Config\SourceConfig\SourcesConfig; use Igancev\WorkReporter\Destination\ConcreteDestinationFactory; use Igancev\WorkReporter\Destination\DestinationType; @@ -16,7 +17,6 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; -use RuntimeException; #[CoversClass(ConcreteDestinationFactory::class)] class ConcreteDestinationFactoryTest extends TestCase @@ -37,7 +37,7 @@ public function testBuildYouTrackDestinationSuccessfully(): void $destinationsConfig = new DestinationsConfig(youTrack: $youTrackConfig); $config = $this->createConfig($destinationsConfig); - $this->configProvider->method('get')->willReturn($config); + $this->configProvider->method('getConfig')->willReturn($config); // Act $destination = $this->factory->build(DestinationType::YouTrack); @@ -46,28 +46,12 @@ public function testBuildYouTrackDestinationSuccessfully(): void $this->assertInstanceOf(YouTrackDestination::class, $destination); } - public function testBuildYouTrackDestinationThrowsExceptionWhenConfigMissing(): void - { - // Arrange - $destinationsConfig = new DestinationsConfig(youTrack: null); - $config = $this->createConfig($destinationsConfig); - - $this->configProvider->method('get')->willReturn($config); - - // Assert - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('YouTrack destination configuration is missing'); - - // Act - $this->factory->build(DestinationType::YouTrack); - } - private function createConfig(DestinationsConfig $destinations): Config { return new Config( source: SourceType::PlainJson, destination: DestinationType::YouTrack, - sources: $this->createStub(SourcesConfig::class), + sources: new SourcesConfig(plainJson: new PlainJsonConfig(filePath: 'path/to/file.json')), destinations: $destinations, ); } diff --git a/tests/Unit/Source/ConcreteSourceFactoryTest.php b/tests/Unit/Source/ConcreteSourceFactoryTest.php index 801b056..73c1a0d 100644 --- a/tests/Unit/Source/ConcreteSourceFactoryTest.php +++ b/tests/Unit/Source/ConcreteSourceFactoryTest.php @@ -7,6 +7,7 @@ use Igancev\WorkReporter\Config\Config; use Igancev\WorkReporter\Config\ConfigProvider; use Igancev\WorkReporter\Config\DestinationConfig\DestinationsConfig; +use Igancev\WorkReporter\Config\DestinationConfig\YouTrackConfig; use Igancev\WorkReporter\Config\SourceConfig\PlainJsonConfig; use Igancev\WorkReporter\Config\SourceConfig\SourcesConfig; use Igancev\WorkReporter\Config\SourceConfig\SuperProductivityConfig; @@ -18,7 +19,6 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\Stub; use PHPUnit\Framework\TestCase; -use RuntimeException; #[CoversClass(ConcreteSourceFactory::class)] class ConcreteSourceFactoryTest extends TestCase @@ -37,9 +37,9 @@ public function testBuildPlainJsonSourceSuccessfully(): void // Arrange $plainJsonConfig = new PlainJsonConfig(filePath: 'path/to/file.json'); $sourcesConfig = new SourcesConfig(plainJson: $plainJsonConfig); - $config = $this->createConfig($sourcesConfig); + $config = $this->createConfig(SourceType::PlainJson, $sourcesConfig); - $this->configProvider->method('get')->willReturn($config); + $this->configProvider->method('getConfig')->willReturn($config); // Act $source = $this->factory->build(SourceType::PlainJson); @@ -61,9 +61,9 @@ public function testBuildSuperProductivitySourceSuccessfully(): void try { $superProductivityConfig = new SuperProductivityConfig(syncFilePath: $tempFile); $sourcesConfig = new SourcesConfig(superProductivity: $superProductivityConfig); - $config = $this->createConfig($sourcesConfig); + $config = $this->createConfig(SourceType::SuperProductivity, $sourcesConfig); - $this->configProvider->method('get')->willReturn($config); + $this->configProvider->method('getConfig')->willReturn($config); // Act $source = $this->factory->build(SourceType::SuperProductivity); @@ -75,45 +75,13 @@ public function testBuildSuperProductivitySourceSuccessfully(): void } } - public function testBuildPlainJsonSourceThrowsExceptionWhenConfigMissing(): void - { - // Arrange - $sourcesConfig = new SourcesConfig(plainJson: null); - $config = $this->createConfig($sourcesConfig); - - $this->configProvider->method('get')->willReturn($config); - - // Assert - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('PlainJson source configuration is missing'); - - // Act - $this->factory->build(SourceType::PlainJson); - } - - public function testBuildSuperProductivitySourceThrowsExceptionWhenConfigMissing(): void - { - // Arrange - $sourcesConfig = new SourcesConfig(superProductivity: null); - $config = $this->createConfig($sourcesConfig); - - $this->configProvider->method('get')->willReturn($config); - - // Assert - $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('SuperProductivity source configuration is missing'); - - // Act - $this->factory->build(SourceType::SuperProductivity); - } - - private function createConfig(SourcesConfig $sources): Config + private function createConfig(SourceType $sourceType, SourcesConfig $sources): Config { return new Config( - source: SourceType::PlainJson, + source: $sourceType, destination: DestinationType::YouTrack, sources: $sources, - destinations: $this->createStub(DestinationsConfig::class), + destinations: new DestinationsConfig(youTrack: new YouTrackConfig('http://yt.local', 'token-abc')), ); } } diff --git a/tests/Unit/Source/SuperProductivity/StorageTest.php b/tests/Unit/Source/SuperProductivity/StorageTest.php index 78e81b2..08a407a 100644 --- a/tests/Unit/Source/SuperProductivity/StorageTest.php +++ b/tests/Unit/Source/SuperProductivity/StorageTest.php @@ -4,11 +4,11 @@ namespace Tests\Unit\Source\SuperProductivity; +use Igancev\WorkReporter\Source\SourceException; use Igancev\WorkReporter\Source\SuperProductivity\Storage; use Igancev\WorkReporter\Source\SuperProductivity\Tag; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\TestCase; -use RuntimeException; #[CoversClass(Storage::class)] final class StorageTest extends TestCase @@ -109,7 +109,7 @@ public function testGetTaskByIdThrowsExceptionIfNotFound(): void $storage = new Storage($this->tempFile); // Assert - $this->expectException(RuntimeException::class); + $this->expectException(SourceException::class); $this->expectExceptionMessage('SuperProductivitySyncDataSource: Unable to find task with id non-existent'); // Act @@ -158,7 +158,7 @@ public function testGetTagByIdThrowsExceptionIfNotFound(): void $storage = new Storage($this->tempFile); // Assert - $this->expectException(RuntimeException::class); + $this->expectException(SourceException::class); $this->expectExceptionMessage('SuperProductivitySyncDataSource: Unable to find tag with id non-existent'); // Act @@ -173,9 +173,9 @@ public function testParseJsonThrowsExceptionIfFileNotReadable(): void chmod($unreadableFile, 0000); // Assert - $this->expectException(RuntimeException::class); + $this->expectException(SourceException::class); $this->expectExceptionMessage( - 'SuperProductivitySyncDataSource: Unable to read sync data file ' . $unreadableFile, + 'SuperProductivitySyncDataSource: Unable to read sync data file: ' . $unreadableFile, ); try { @@ -192,7 +192,7 @@ public function testParseJsonThrowsExceptionIfNoJsonFound(): void file_put_contents($this->tempFile, 'no-curly-braces-here'); // Assert - $this->expectException(RuntimeException::class); + $this->expectException(SourceException::class); $this->expectExceptionMessage( 'SuperProductivitySyncDataSource: Unable to parse start position "{" ' . $this->tempFile, ); @@ -207,7 +207,7 @@ public function testParseJsonThrowsExceptionOnInvalidJson(): void file_put_contents($this->tempFile, 'pf_4.4__{invalid-json}'); // Assert - $this->expectException(RuntimeException::class); + $this->expectException(SourceException::class); $this->expectExceptionMessage('SuperProductivitySyncDataSource: Unable to parse JSON:'); // Act diff --git a/tests/Unit/WorkReportCommandTest.php b/tests/Unit/WorkReportCommandTest.php index d24dd62..d7541ab 100644 --- a/tests/Unit/WorkReportCommandTest.php +++ b/tests/Unit/WorkReportCommandTest.php @@ -12,6 +12,8 @@ use Igancev\WorkReporter\Config\ConfigException; use Igancev\WorkReporter\Config\ConfigProvider; use Igancev\WorkReporter\Config\DestinationConfig\DestinationsConfig; +use Igancev\WorkReporter\Config\DestinationConfig\YouTrackConfig; +use Igancev\WorkReporter\Config\SourceConfig\PlainJsonConfig; use Igancev\WorkReporter\Config\SourceConfig\SourcesConfig; use Igancev\WorkReporter\Destination\DeliveryEvent; use Igancev\WorkReporter\Destination\DeliveryStream; @@ -60,11 +62,13 @@ protected function setUp(): void $config = new Config( SourceType::PlainJson, DestinationType::YouTrack, - new SourcesConfig(), - new DestinationsConfig(), + new SourcesConfig(plainJson: new PlainJsonConfig(filePath: 'path/to/time-entries.json')), + new DestinationsConfig( + youTrack: new YouTrackConfig(url: 'https://example.com', token: 'token'), + ), ); $configProvider = $this->createStub(ConfigProvider::class); - $configProvider->method('get')->willReturn($config); + $configProvider->method('getConfig')->willReturn($config); $command = new WorkReportCommand($sourceFactory, $destinationFactory, $configProvider); $this->tester = new CommandTester($command); @@ -452,7 +456,7 @@ public function testConfigExceptionReturnsFailure(): void { // Arrange $configProvider = $this->createMock(ConfigProvider::class); - $configProvider->method('get')->willThrowException(new ConfigException('Config error')); + $configProvider->method('getConfig')->willThrowException(new ConfigException('Config error')); $command = new WorkReportCommand( $this->createStub(TimeEntriesSourceFactory::class),