From da2835bf7d138e24118f720883003a0fe6f3424a Mon Sep 17 00:00:00 2001 From: Daniel Santos Date: Thu, 2 Apr 2026 09:21:26 +0100 Subject: [PATCH 1/2] Increase test coverage --- .../ArchitectureSnifferTest.php | 171 +++++++++++++ .../Configuration/ArchitectureLibraryTest.php | 169 +++++++++++++ .../Configuration/GroupTest.php | 178 +++++++++++++ .../Configuration/Rules/AbstractRuleTest.php | 49 ++++ .../Configuration/Rules/MustBeFinalTest.php | 59 +++++ .../Rules/MustBeReadonlyTest.php | 36 +++ .../Configuration/Rules/MustExtendTest.php | 84 +++++++ .../Configuration/Rules/MustImplementTest.php | 84 +++++++ .../Rules/MustNotDependOnTest.php | 60 +++++ .../Rules/MustOnlyDependOnTest.php | 60 +++++ .../MustOnlyHaveOnePublicMethodNamedTest.php | 60 +++++ .../Rules/MustOnlyHaveOnePublicMethodTest.php | 36 +++ .../Selector/ClassSelectorTest.php | 38 +++ .../Selector/InterfaceClassSelectorTest.php | 38 +++ .../Selector/NamespaceSelectorTest.php | 38 +++ .../Configuration/Selector/RegexTraitTest.php | 32 +++ .../Helper/GroupFlattenerTest.php | 136 ++++++++++ .../Helper/ProjectPathResolverTest.php | 25 ++ .../Helper/RuleBuilderTest.php | 236 ++++++++++++++++++ .../Helper/SelectorBuilderTest.php | 34 +++ .../Helper/TypeCheckerTest.php | 73 ++++++ .../CsFixer/Command/CsFixerCommandTest.php | 60 +++++ .../Unit/Kununu/CsFixer/CsFixerPluginTest.php | 86 +++++++ .../Provider/CsFixerCommandProviderTest.php | 23 ++ 24 files changed, 1865 insertions(+) create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/ArchitectureSnifferTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/AbstractRuleTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeReadonlyTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustNotDependOnTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOnTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/ClassSelectorTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelectorTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/NamespaceSelectorTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/ProjectPathResolverTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/RuleBuilderTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/SelectorBuilderTest.php create mode 100644 tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php create mode 100644 tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php create mode 100644 tests/Unit/Kununu/CsFixer/Provider/CsFixerCommandProviderTest.php diff --git a/tests/Unit/Kununu/ArchitectureSniffer/ArchitectureSnifferTest.php b/tests/Unit/Kununu/ArchitectureSniffer/ArchitectureSnifferTest.php new file mode 100644 index 0000000..6dd669e --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/ArchitectureSnifferTest.php @@ -0,0 +1,171 @@ +architectureFile = ProjectPathResolver::resolve('architecture.yaml'); + $this->architectureDir = dirname($this->architectureFile); + + if (!is_dir($this->architectureDir)) { + mkdir($this->architectureDir, 0777, true); + } + } + + protected function tearDown(): void + { + if (is_file($this->architectureFile)) { + unlink($this->architectureFile); + } + if (is_dir($this->architectureDir)) { + @rmdir($this->architectureDir); + } + } + + public function testTestArchitectureYieldsRulesForValidConfig(): void + { + $this->writeYaml([ + 'architecture' => [ + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'depends_on' => ['App\\Repository\\'], + 'final' => true, + ], + ], + ]); + + $sniffer = new ArchitectureSniffer(); + $rules = iterator_to_array($sniffer->testArchitecture()); + + self::assertNotEmpty($rules); + foreach ($rules as $rule) { + self::assertInstanceOf(Rule::class, $rule); + } + } + + public function testTestArchitectureThrowsWhenArchitectureKeyMissing(): void + { + $this->writeYaml([ + 'something_else' => [], + ]); + + $sniffer = new ArchitectureSniffer(); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('"architecture" key is missing'); + + iterator_to_array($sniffer->testArchitecture()); + } + + public function testTestArchitectureThrowsWhenGroupsNotStringKeyed(): void + { + $this->writeYaml([ + 'architecture' => ['not-string-keyed'], + ]); + + $sniffer = new ArchitectureSniffer(); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('"groups" must be a non-empty array'); + + iterator_to_array($sniffer->testArchitecture()); + } + + public function testTestArchitectureThrowsWhenNoGroupHasIncludes(): void + { + $this->writeYaml([ + 'architecture' => [ + 'services' => [ + 'final' => true, + ], + ], + ]); + + $sniffer = new ArchitectureSniffer(); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('"includes" property'); + + iterator_to_array($sniffer->testArchitecture()); + } + + public function testTestArchitectureThrowsWhenNoGroupHasDependsOn(): void + { + $this->writeYaml([ + 'architecture' => [ + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'final' => true, + ], + ], + ]); + + $sniffer = new ArchitectureSniffer(); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('"dependsOn" property'); + + iterator_to_array($sniffer->testArchitecture()); + } + + public function testTestArchitectureThrowsWhenGlobalNamespaceGroupHasDependsOn(): void + { + $this->writeYaml([ + 'architecture' => [ + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'depends_on' => ['App\\Repository\\'], + ], + 'external' => [ + 'includes' => ['Vendor\\Package\\SomeClass'], + 'depends_on' => ['App\\Service\\'], + ], + ], + ]); + + $sniffer = new ArchitectureSniffer(); + + self::expectException(InvalidArgumentException::class); + self::expectExceptionMessage('global namespace'); + + iterator_to_array($sniffer->testArchitecture()); + } + + public function testTestArchitectureWithGlobalNamespaceWithoutDependsOn(): void + { + $this->writeYaml([ + 'architecture' => [ + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'depends_on' => ['App\\Repository\\'], + ], + 'external' => [ + 'includes' => ['Vendor\\Package\\SomeClass'], + ], + ], + ]); + + $sniffer = new ArchitectureSniffer(); + $rules = iterator_to_array($sniffer->testArchitecture()); + + self::assertNotEmpty($rules); + } + + private function writeYaml(array $data): void + { + file_put_contents($this->architectureFile, Yaml::dump($data, 4)); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php new file mode 100644 index 0000000..6f85783 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php @@ -0,0 +1,169 @@ +createLibrary(); + + $group = $library->getGroupBy('services'); + + self::assertSame('services', $group->name); + self::assertSame(['App\\Service\\UserService'], $group->flattenedIncludes); + } + + public function testConstructorThrowsOnNonStringIncludes(): void + { + self::expectException(InvalidArgumentException::class); + + new ArchitectureLibrary([ + 'broken' => [ + 'includes' => [1, 2, 3], + ], + ]); + } + + public function testGetGroupByThrowsOnMissingGroup(): void + { + $library = $this->createLibrary(); + + self::expectException(InvalidArgumentException::class); + + $library->getGroupBy('nonexistent'); + } + + public function testResolveTargetsWithoutDependsOnRule(): void + { + $library = $this->createLibrary(); + $group = $library->getGroupBy('services'); + + $result = $library->resolveTargets($group, ['App\\Repository\\UserRepository']); + + self::assertSame(['App\\Repository\\UserRepository'], $result); + } + + public function testResolveTargetsWithDependsOnRuleIncludesGroupIncludes(): void + { + $library = $this->createLibraryWithDeps(); + $group = $library->getGroupBy('services'); + + $result = $library->resolveTargets($group, ['App\\Repository\\'], true); + + self::assertContains('App\\Service\\UserService', $result); + self::assertContains('App\\Repository\\', $result); + } + + public function testResolveTargetsWithExtendsAndImplements(): void + { + $library = new ArchitectureLibrary([ + 'handlers' => [ + 'includes' => ['App\\Handler\\CreateHandler'], + 'extends' => 'App\\Handler\\AbstractHandler', + 'implements' => ['App\\Contract\\HandlerInterface'], + 'depends_on' => ['App\\Service\\'], + ], + ]); + $group = $library->getGroupBy('handlers'); + + $result = $library->resolveTargets($group, $group->dependsOn, true); + + self::assertContains('App\\Handler\\CreateHandler', $result); + self::assertContains('App\\Handler\\AbstractHandler', $result); + self::assertContains('App\\Contract\\HandlerInterface', $result); + self::assertContains('App\\Service\\', $result); + } + + public function testResolveTargetsWithGroupReference(): void + { + $library = $this->createLibraryWithDeps(); + $group = $library->getGroupBy('services'); + + $result = $library->resolveTargets($group, ['repositories']); + + self::assertContains('App\\Repository\\UserRepository', $result); + } + + public function testFindTargetExcludesReturnsExcludesFromGroup(): void + { + $library = new ArchitectureLibrary([ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + 'excludes' => ['App\\Service\\Internal\\'], + ], + ]); + + $group = $library->getGroupBy('services'); + $targets = ['App\\Service\\UserService']; + $result = $library->findTargetExcludes(['services'], $targets); + + self::assertSame(['App\\Service\\Internal\\'], $result); + } + + public function testFindTargetExcludesReturnsEmptyForNonGroupTargets(): void + { + $library = $this->createLibrary(); + + $result = $library->findTargetExcludes(['App\\Unknown\\Class'], ['App\\Unknown\\Class']); + + self::assertSame([], $result); + } + + public function testFindTargetExcludesFiltersOutAlreadyIncludedTargets(): void + { + $library = new ArchitectureLibrary([ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + 'excludes' => ['App\\Service\\Internal\\', 'App\\Service\\UserService'], + ], + ]); + + $result = $library->findTargetExcludes(['services'], ['App\\Service\\UserService']); + + self::assertNotContains('App\\Service\\UserService', $result); + self::assertContains('App\\Service\\Internal\\', $result); + } + + public function testConstructorHandlesExcludesAsNonStringArray(): void + { + $library = new ArchitectureLibrary([ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + 'excludes' => [1, 2], + ], + ]); + + $group = $library->getGroupBy('services'); + + self::assertNull($group->flattenedExcludes); + } + + private function createLibrary(): ArchitectureLibrary + { + return new ArchitectureLibrary([ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + ], + ]); + } + + private function createLibraryWithDeps(): ArchitectureLibrary + { + return new ArchitectureLibrary([ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + 'depends_on' => ['repositories'], + ], + 'repositories' => [ + 'includes' => ['App\\Repository\\UserRepository'], + ], + ]); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php new file mode 100644 index 0000000..07df68c --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php @@ -0,0 +1,178 @@ + ['App\\Repository\\'], + Group::MUST_NOT_DEPEND_ON_KEY => ['App\\Controller\\'], + Group::EXTENDS_KEY => 'App\\Base\\AbstractService', + Group::IMPLEMENTS_KEY => ['App\\Contract\\ServiceInterface'], + Group::FINAL_KEY => true, + Group::READONLY_KEY => true, + Group::MUST_ONLY_HAVE_ONE_PUBLIC_METHOD_NAMED_KEY => 'execute', + ], + flattenedExcludes: ['App\\Service\\Internal\\'], + ); + + self::assertSame('TestGroup', $group->name); + self::assertSame(['App\\Service\\'], $group->flattenedIncludes); + self::assertSame(['App\\Service\\Internal\\'], $group->flattenedExcludes); + self::assertSame(['App\\Repository\\'], $group->dependsOn); + self::assertSame(['App\\Controller\\'], $group->mustNotDependOn); + self::assertSame('App\\Base\\AbstractService', $group->extends); + self::assertSame(['App\\Contract\\ServiceInterface'], $group->implements); + self::assertTrue($group->isFinal); + self::assertTrue($group->isReadonly); + self::assertSame('execute', $group->mustOnlyHaveOnePublicMethodName); + } + + public function testBuildFromWithMinimalAttributes(): void + { + $group = Group::buildFrom( + groupName: 'MinimalGroup', + flattenedIncludes: ['App\\Entity\\'], + targetAttributes: [ + Group::INCLUDES_KEY => ['App\\Entity\\'], + ], + flattenedExcludes: null, + ); + + self::assertSame('MinimalGroup', $group->name); + self::assertSame(['App\\Entity\\'], $group->flattenedIncludes); + self::assertNull($group->flattenedExcludes); + self::assertNull($group->dependsOn); + self::assertNull($group->mustNotDependOn); + self::assertNull($group->extends); + self::assertNull($group->implements); + self::assertFalse($group->isFinal); + self::assertFalse($group->isReadonly); + self::assertNull($group->mustOnlyHaveOnePublicMethodName); + } + + public function testBuildFromWithFinalFalse(): void + { + $group = Group::buildFrom( + groupName: 'NotFinal', + flattenedIncludes: ['App\\'], + targetAttributes: [ + Group::FINAL_KEY => false, + Group::READONLY_KEY => false, + ], + flattenedExcludes: null, + ); + + self::assertFalse($group->isFinal); + self::assertFalse($group->isReadonly); + } + + public function testBuildFromWithNonStringExtends(): void + { + $group = Group::buildFrom( + groupName: 'BadExtends', + flattenedIncludes: ['App\\'], + targetAttributes: [ + Group::EXTENDS_KEY => 123, + ], + flattenedExcludes: null, + ); + + self::assertNull($group->extends); + } + + public function testBuildFromWithNonStringMethodName(): void + { + $group = Group::buildFrom( + groupName: 'BadMethod', + flattenedIncludes: ['App\\'], + targetAttributes: [ + Group::MUST_ONLY_HAVE_ONE_PUBLIC_METHOD_NAMED_KEY => true, + ], + flattenedExcludes: null, + ); + + self::assertNull($group->mustOnlyHaveOnePublicMethodName); + } + + #[DataProvider('shouldMethodsProvider')] + public function testShouldMethods( + Group $group, + bool $shouldBeFinal, + bool $shouldBeReadonly, + bool $shouldExtend, + bool $shouldNotDependOn, + bool $shouldDependOn, + bool $shouldImplement, + bool $shouldOnlyHaveOnePublicMethodNamed, + ): void { + self::assertSame($shouldBeFinal, $group->shouldBeFinal()); + self::assertSame($shouldBeReadonly, $group->shouldBeReadonly()); + self::assertSame($shouldExtend, $group->shouldExtend()); + self::assertSame($shouldNotDependOn, $group->shouldNotDependOn()); + self::assertSame($shouldDependOn, $group->shouldDependOn()); + self::assertSame($shouldImplement, $group->shouldImplement()); + self::assertSame($shouldOnlyHaveOnePublicMethodNamed, $group->shouldOnlyHaveOnePublicMethodNamed()); + } + + public static function shouldMethodsProvider(): array + { + return [ + 'all enabled' => [ + new Group( + name: 'Full', + flattenedIncludes: ['App\\'], + flattenedExcludes: null, + dependsOn: ['Dep\\'], + mustNotDependOn: ['Bad\\'], + extends: 'App\\Base', + implements: ['App\\ContractInterface'], + isFinal: true, + isReadonly: true, + mustOnlyHaveOnePublicMethodName: 'run', + ), + true, true, true, true, true, true, true, + ], + 'all disabled' => [ + new Group( + name: 'Empty', + flattenedIncludes: ['App\\'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ), + false, false, false, false, false, false, false, + ], + 'empty arrays are falsy' => [ + new Group( + name: 'EmptyArrays', + flattenedIncludes: ['App\\'], + flattenedExcludes: null, + dependsOn: [], + mustNotDependOn: [], + extends: null, + implements: [], + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: '', + ), + false, false, false, false, false, false, false, + ], + ]; + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/AbstractRuleTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/AbstractRuleTest.php new file mode 100644 index 0000000..3b4d2e6 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/AbstractRuleTest.php @@ -0,0 +1,49 @@ +getMessage()); + self::assertStringContainsString('TestGroup', $exception->getMessage()); + self::assertStringContainsString('final', $exception->getMessage()); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php new file mode 100644 index 0000000..209c725 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php @@ -0,0 +1,59 @@ + ['includes' => ['App\\Service\\MyService'], 'final' => true], + ]); + + $result = MustBeFinal::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleWithExcludes(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: ['App\\Service\\Internal\\'], + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: true, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService'], 'excludes' => ['App\\Service\\Internal\\'], 'final' => true], + ]); + + $result = MustBeFinal::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeReadonlyTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeReadonlyTest.php new file mode 100644 index 0000000..79363fa --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeReadonlyTest.php @@ -0,0 +1,36 @@ + ['includes' => ['App\\Service\\MyService'], 'readonly' => true], + ]); + + $result = MustBeReadonly::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php new file mode 100644 index 0000000..fcddb28 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php @@ -0,0 +1,84 @@ + ['includes' => ['App\\Service\\MyService'], 'extends' => 'App\\Base\\AbstractService'], + ]); + + $result = MustExtend::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleThrowsWhenExtendsIsNull(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService']], + ]); + + self::expectException(LogicException::class); + + MustExtend::createRule($group, $library); + } + + public function testCreateRuleThrowsWhenIncludesContainInterface(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Contract\\ServiceInterface'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: 'App\\Base\\AbstractService', + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Contract\\ServiceInterface'], 'extends' => 'App\\Base\\AbstractService'], + ]); + + self::expectException(InvalidArgumentException::class); + + MustExtend::createRule($group, $library); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php new file mode 100644 index 0000000..c6fdf30 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php @@ -0,0 +1,84 @@ + ['includes' => ['App\\Service\\MyService'], 'implements' => ['App\\Contract\\ServiceInterface']], + ]); + + $result = MustImplement::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleThrowsWhenImplementsIsNull(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService']], + ]); + + self::expectException(LogicException::class); + + MustImplement::createRule($group, $library); + } + + public function testCreateRuleThrowsWhenTargetIsNotInterface(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: ['App\\Service\\ConcreteClass'], + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService'], 'implements' => ['App\\Service\\ConcreteClass']], + ]); + + self::expectException(InvalidArgumentException::class); + + MustImplement::createRule($group, $library); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustNotDependOnTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustNotDependOnTest.php new file mode 100644 index 0000000..8d644fa --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustNotDependOnTest.php @@ -0,0 +1,60 @@ + ['includes' => ['App\\Service\\MyService'], 'must_not_depend_on' => ['App\\Controller\\']], + ]); + + $result = MustNotDependOn::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleThrowsWhenMustNotDependOnIsNull(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService']], + ]); + + self::expectException(LogicException::class); + + MustNotDependOn::createRule($group, $library); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOnTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOnTest.php new file mode 100644 index 0000000..498adb5 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyDependOnTest.php @@ -0,0 +1,60 @@ + ['includes' => ['App\\Service\\MyService'], 'depends_on' => ['App\\Repository\\']], + ]); + + $result = MustOnlyDependOn::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleThrowsWhenDependsOnIsNull(): void + { + $group = new Group( + name: 'services', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'services' => ['includes' => ['App\\Service\\MyService']], + ]); + + self::expectException(LogicException::class); + + MustOnlyDependOn::createRule($group, $library); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php new file mode 100644 index 0000000..dc5f6f7 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php @@ -0,0 +1,60 @@ + ['includes' => ['App\\Handler\\MyHandler'], 'must_only_have_one_public_method_named' => 'handle'], + ]); + + $result = MustOnlyHaveOnePublicMethodNamed::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } + + public function testCreateRuleThrowsWhenMethodNameIsNull(): void + { + $group = new Group( + name: 'handlers', + flattenedIncludes: ['App\\Handler\\MyHandler'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = new ArchitectureLibrary([ + 'handlers' => ['includes' => ['App\\Handler\\MyHandler']], + ]); + + self::expectException(LogicException::class); + + MustOnlyHaveOnePublicMethodNamed::createRule($group, $library); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php new file mode 100644 index 0000000..5fdebf9 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php @@ -0,0 +1,36 @@ + ['includes' => ['App\\Handler\\MyHandler'], 'must_only_have_one_public_method_named' => 'handle'], + ]); + + $result = MustOnlyHaveOnePublicMethod::createRule($group, $library); + + self::assertInstanceOf(Rule::class, $result); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/ClassSelectorTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/ClassSelectorTest.php new file mode 100644 index 0000000..e5b79d2 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/ClassSelectorTest.php @@ -0,0 +1,38 @@ +getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorWithWildcard(): void + { + $selector = new ClassSelector('App\\*\\MyService'); + + $result = $selector->getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorThrowsOnEmptyString(): void + { + self::expectException(InvalidArgumentException::class); + + $selector = new ClassSelector(''); + $selector->getPHPatSelector(); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelectorTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelectorTest.php new file mode 100644 index 0000000..e499fd4 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/InterfaceClassSelectorTest.php @@ -0,0 +1,38 @@ +getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorWithWildcard(): void + { + $selector = new InterfaceClassSelector('App\\*\\ServiceInterface'); + + $result = $selector->getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorThrowsOnEmptyString(): void + { + self::expectException(InvalidArgumentException::class); + + $selector = new InterfaceClassSelector(''); + $selector->getPHPatSelector(); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/NamespaceSelectorTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/NamespaceSelectorTest.php new file mode 100644 index 0000000..d723f74 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/NamespaceSelectorTest.php @@ -0,0 +1,38 @@ +getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorWithWildcard(): void + { + $selector = new NamespaceSelector('App\\*\\Service\\'); + + $result = $selector->getPHPatSelector(); + + self::assertInstanceOf(SelectorInterface::class, $result); + } + + public function testGetPHPatSelectorThrowsOnEmptyString(): void + { + self::expectException(InvalidArgumentException::class); + + $selector = new NamespaceSelector(''); + $selector->getPHPatSelector(); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php new file mode 100644 index 0000000..419905e --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Selector/RegexTraitTest.php @@ -0,0 +1,32 @@ +makeRegex($path, $file)); + } + + public static function makeRegexProvider(): array + { + return [ + 'plain class path' => ['App\\Service\\MyService', false, 'App\\Service\\MyService'], + 'wildcard converts to regex' => ['App\\*\\MyService', false, '/App\\\\.+\\\\MyService/'], + 'wildcard with leading backslash' => ['\\App\\*\\MyService', false, '/App\\\\.+\\\\MyService/'], + 'file mode prepends backslash' => ['App\\Service\\MyService', true, '\\App\\Service\\MyService'], + 'file mode with leading backslash' => ['\\App\\Service\\MyService', true, '\\App\\Service\\MyService'], + 'wildcard ignores file mode' => ['App\\*', false, '/App\\\\.+/'], + 'namespace path unchanged' => ['App\\Repository\\', false, 'App\\Repository\\'], + ]; + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php new file mode 100644 index 0000000..dfe2418 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php @@ -0,0 +1,136 @@ + [ + 'includes' => ['App\\Service\\UserService', 'App\\Service\\OrderService'], + ], + ]; + + $result = GroupFlattener::flattenIncludes('services', ['App\\Service\\UserService', 'App\\Service\\OrderService']); + + self::assertSame(['App\\Service\\UserService', 'App\\Service\\OrderService'], $result); + } + + public function testFlattenIncludesWithNestedGroupReference(): void + { + GroupFlattener::$groups = [ + 'parent' => [ + 'includes' => ['child'], + ], + 'child' => [ + 'includes' => ['App\\Entity\\User', 'App\\Entity\\Order'], + ], + ]; + + $result = GroupFlattener::flattenIncludes('parent', ['child']); + + self::assertSame(['App\\Entity\\User', 'App\\Entity\\Order'], $result); + } + + public function testFlattenIncludesSkipsCircularReferences(): void + { + GroupFlattener::$groups = [ + 'groupA' => [ + 'includes' => ['groupB'], + ], + 'groupB' => [ + 'includes' => ['groupA'], + ], + ]; + + $result = GroupFlattener::flattenIncludes('groupA', ['groupB']); + + self::assertSame([], $result); + } + + public function testFlattenIncludesThrowsOnNonArrayKey(): void + { + GroupFlattener::$groups = [ + 'parent' => [ + 'includes' => ['child'], + ], + 'child' => [ + 'includes' => 'not-an-array', + ], + ]; + + self::expectException(InvalidArgumentException::class); + + GroupFlattener::flattenIncludes('parent', ['child']); + } + + public function testFlattenExcludesWithDirectFqcns(): void + { + GroupFlattener::$groups = [ + 'services' => [ + 'includes' => ['App\\Service\\'], + 'excludes' => ['App\\Service\\Internal\\'], + ], + ]; + + $result = GroupFlattener::flattenExcludes('services', ['App\\Service\\Internal\\'], ['App\\Service\\']); + + self::assertSame(['App\\Service\\Internal\\'], $result); + } + + public function testFlattenExcludesReturnsNullWhenEmpty(): void + { + GroupFlattener::$groups = [ + 'services' => [ + 'includes' => ['App\\Service\\'], + ], + ]; + + $result = GroupFlattener::flattenExcludes('services', [], ['App\\Service\\']); + + self::assertNull($result); + } + + public function testFlattenExcludesReturnsNullWhenAllExcludesOverlapIncludes(): void + { + GroupFlattener::$groups = [ + 'services' => [ + 'includes' => ['App\\Service\\UserService'], + 'excludes' => ['App\\Service\\UserService'], + ], + ]; + + $result = GroupFlattener::flattenExcludes('services', ['App\\Service\\UserService'], ['App\\Service\\UserService']); + + self::assertNull($result); + } + + public function testFlattenExcludesWithNestedGroupReference(): void + { + GroupFlattener::$groups = [ + 'parent' => [ + 'includes' => ['App\\'], + 'excludes' => ['child'], + ], + 'child' => [ + 'excludes' => ['App\\Internal\\Secret'], + ], + ]; + + $result = GroupFlattener::flattenExcludes('parent', ['child'], ['App\\']); + + self::assertSame(['App\\Internal\\Secret'], $result); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Helper/ProjectPathResolverTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Helper/ProjectPathResolverTest.php new file mode 100644 index 0000000..9432ef8 --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Helper/ProjectPathResolverTest.php @@ -0,0 +1,25 @@ +createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(0, $rules); + } + + public function testGetRulesYieldsFinalRule(): void + { + $group = new Group( + name: 'finalOnly', + flattenedIncludes: ['App\\Service\\'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: true, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + self::assertInstanceOf(PHPatRule::class, $rules[0]); + } + + public function testGetRulesYieldsReadonlyRule(): void + { + $group = new Group( + name: 'readonlyOnly', + flattenedIncludes: ['App\\Service\\'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: true, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + } + + public function testGetRulesYieldsExtendRule(): void + { + $group = new Group( + name: 'extendsOnly', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: 'App\\Base\\AbstractService', + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + } + + public function testGetRulesYieldsImplementRule(): void + { + $group = new Group( + name: 'implementsOnly', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: ['App\\Contract\\ServiceInterface'], + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + } + + public function testGetRulesYieldsDependOnRule(): void + { + $group = new Group( + name: 'dependsOnly', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: ['App\\Repository\\'], + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + } + + public function testGetRulesYieldsMustNotDependOnRule(): void + { + $group = new Group( + name: 'notDependOnly', + flattenedIncludes: ['App\\Service\\MyService'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: ['App\\Controller\\'], + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: null, + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(1, $rules); + } + + public function testGetRulesYieldsPublicMethodRules(): void + { + $group = new Group( + name: 'publicMethodOnly', + flattenedIncludes: ['App\\Handler\\MyHandler'], + flattenedExcludes: null, + dependsOn: null, + mustNotDependOn: null, + extends: null, + implements: null, + isFinal: false, + isReadonly: false, + mustOnlyHaveOnePublicMethodName: 'handle', + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(2, $rules); + } + + public function testGetRulesYieldsAllRulesForFullGroup(): void + { + $group = new Group( + name: 'fullGroup', + flattenedIncludes: ['App\\Handler\\MyHandler'], + flattenedExcludes: null, + dependsOn: ['App\\Service\\'], + mustNotDependOn: ['App\\Controller\\'], + extends: 'App\\Base\\AbstractHandler', + implements: ['App\\Contract\\HandlerInterface'], + isFinal: true, + isReadonly: true, + mustOnlyHaveOnePublicMethodName: 'execute', + ); + $library = $this->createLibraryForGroup($group); + + $rules = iterator_to_array(RuleBuilder::getRules($group, $library)); + + self::assertCount(8, $rules); + } + + private function createLibraryForGroup(Group $group): ArchitectureLibrary + { + $attributes = [ + 'includes' => $group->flattenedIncludes, + ]; + + if ($group->dependsOn !== null) { + $attributes['depends_on'] = $group->dependsOn; + } + if ($group->mustNotDependOn !== null) { + $attributes['must_not_depend_on'] = $group->mustNotDependOn; + } + if ($group->extends !== null) { + $attributes['extends'] = $group->extends; + } + if ($group->implements !== null) { + $attributes['implements'] = $group->implements; + } + if ($group->isFinal) { + $attributes['final'] = true; + } + if ($group->isReadonly) { + $attributes['readonly'] = true; + } + if ($group->mustOnlyHaveOnePublicMethodName !== null) { + $attributes['must_only_have_one_public_method_named'] = $group->mustOnlyHaveOnePublicMethodName; + } + + return new ArchitectureLibrary([ + $group->name => $attributes, + ]); + } +} diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Helper/SelectorBuilderTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Helper/SelectorBuilderTest.php new file mode 100644 index 0000000..137bbdd --- /dev/null +++ b/tests/Unit/Kununu/ArchitectureSniffer/Helper/SelectorBuilderTest.php @@ -0,0 +1,34 @@ + [['a' => 1, 'b' => 2], true], + 'empty array' => [[], true], + 'integer keys' => [[1, 2, 3], false], + 'mixed keys' => [['a' => 1, 0 => 2], false], + 'not an array (string)' => ['hello', false], + 'not an array (int)' => [42, false], + 'not an array (null)' => [null, false], + ]; + } + + #[DataProvider('isArrayOfStringsProvider')] + public function testIsArrayOfStrings(mixed $input, bool $expected): void + { + self::assertSame($expected, TypeChecker::isArrayOfStrings($input)); + } + + public static function isArrayOfStringsProvider(): array + { + return [ + 'all strings' => [['a', 'b', 'c'], true], + 'empty array' => [[], true], + 'contains integer' => [['a', 1], false], + 'contains null' => [['a', null], false], + 'not an array (string)' => ['hello', false], + 'not an array (int)' => [42, false], + 'not an array (null)' => [null, false], + ]; + } + + public function testCastArrayOfStringsReturnsValidArray(): void + { + $input = ['foo', 'bar', 'baz']; + + $result = TypeChecker::castArrayOfStrings($input); + + self::assertSame($input, $result); + } + + public function testCastArrayOfStringsThrowsOnInvalidInput(): void + { + self::expectException(InvalidArgumentException::class); + + TypeChecker::castArrayOfStrings([1, 2, 3]); + } + + public function testCastArrayOfStringsThrowsOnNonArray(): void + { + self::expectException(InvalidArgumentException::class); + + TypeChecker::castArrayOfStrings('not-an-array'); + } +} diff --git a/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php b/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php index f883037..c947139 100644 --- a/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php +++ b/tests/Unit/Kununu/CsFixer/Command/CsFixerCommandTest.php @@ -61,6 +61,66 @@ private function contents(string $file): string return $contents; } + public function testCsFixerCommandReturnsFailureWhenNoFilesProvided(): void + { + $application = new Application(); + $command = new CsFixerCommand(); + method_exists($application, 'addCommand') + ? $application->addCommand($command) + : $application->add($command); + + $command = $application->find('kununu:cs-fixer'); + $tester = new CommandTester($command); + + $exitCode = $tester->execute([]); + + self::assertSame(CsFixerCommand::FAILURE, $exitCode); + } + + public function testCsFixerCommandWithExtraArgs(): void + { + $this->tempFile = sys_get_temp_dir() . '/csfixer_' . uniqid('', true) . '.php'; + file_put_contents($this->tempFile, "addCommand($command) + : $application->add($command); + + $command = $application->find('kununu:cs-fixer'); + $tester = new CommandTester($command); + + $exitCode = $tester->execute([ + 'files' => [$this->tempFile], + '--extra-args' => ['--dry-run'], + ]); + + self::assertSame(CsFixerCommand::SUCCESS, $exitCode); + } + + public function testCsFixerCommandReturnsFailureWhenConfigNotFound(): void + { + $this->tempFile = sys_get_temp_dir() . '/csfixer_' . uniqid('', true) . '.php'; + file_put_contents($this->tempFile, "addCommand($command) + : $application->add($command); + + $command = $application->find('kununu:cs-fixer'); + $tester = new CommandTester($command); + + $exitCode = $tester->execute([ + 'files' => [$this->tempFile], + '--config' => '/nonexistent/path/config.php', + ]); + + self::assertSame(CsFixerCommand::FAILURE, $exitCode); + } + protected function tearDown(): void { if ($this->tempFile !== null && is_file($this->tempFile)) { diff --git a/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php b/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php new file mode 100644 index 0000000..ef6d1b6 --- /dev/null +++ b/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php @@ -0,0 +1,86 @@ +createMock(Composer::class); + $io = $this->createMock(IOInterface::class); + + $plugin->activate($composer, $io); + + self::assertInstanceOf(CsFixerPlugin::class, $plugin); + } + + public function testDeactivateDoesNotThrow(): void + { + $plugin = new CsFixerPlugin(); + $composer = $this->createMock(Composer::class); + $io = $this->createMock(IOInterface::class); + + $plugin->deactivate($composer, $io); + + self::assertInstanceOf(CsFixerPlugin::class, $plugin); + } + + public function testUninstallDoesNotThrow(): void + { + $plugin = new CsFixerPlugin(); + $composer = $this->createMock(Composer::class); + $io = $this->createMock(IOInterface::class); + + $plugin->uninstall($composer, $io); + + self::assertInstanceOf(CsFixerPlugin::class, $plugin); + } + + public function testGetCapabilitiesReturnsCommandProvider(): void + { + $plugin = new CsFixerPlugin(); + + $capabilities = $plugin->getCapabilities(); + + self::assertArrayHasKey(CommandProvider::class, $capabilities); + self::assertSame(CsFixerCommandProvider::class, $capabilities[CommandProvider::class]); + } + + public function testAddCsFixerGitHooksExecutesWithoutThrowing(): void + { + $plugin = new CsFixerPlugin(); + $composer = $this->createMock(Composer::class); + $io = $this->createMock(IOInterface::class); + $plugin->activate($composer, $io); + + ob_start(); + try { + $plugin->addCsFixerGitHooks(); + } catch (\Throwable) { + } + ob_end_clean(); + + self::assertInstanceOf(CsFixerPlugin::class, $plugin); + } +} diff --git a/tests/Unit/Kununu/CsFixer/Provider/CsFixerCommandProviderTest.php b/tests/Unit/Kununu/CsFixer/Provider/CsFixerCommandProviderTest.php new file mode 100644 index 0000000..948f9b3 --- /dev/null +++ b/tests/Unit/Kununu/CsFixer/Provider/CsFixerCommandProviderTest.php @@ -0,0 +1,23 @@ +getCommands(); + + self::assertCount(2, $commands); + self::assertInstanceOf(CsFixerCommand::class, $commands[0]); + self::assertInstanceOf(CsFixerGitHookCommand::class, $commands[1]); + } +} From 6bd5c7737f7153378842eb1d82b3e0938e974450 Mon Sep 17 00:00:00 2001 From: Daniel Santos Date: Thu, 2 Apr 2026 09:42:48 +0100 Subject: [PATCH 2/2] Fix CS --- composer.json | 2 +- .../Configuration/ArchitectureLibraryTest.php | 1 - .../ArchitectureSniffer/Configuration/GroupTest.php | 12 ++++++------ .../Configuration/Rules/MustBeFinalTest.php | 6 +++++- .../Configuration/Rules/MustExtendTest.php | 5 ++++- .../Configuration/Rules/MustImplementTest.php | 5 ++++- .../Rules/MustOnlyHaveOnePublicMethodNamedTest.php | 5 ++++- .../Rules/MustOnlyHaveOnePublicMethodTest.php | 5 ++++- .../Helper/GroupFlattenerTest.php | 11 +++++++++-- .../ArchitectureSniffer/Helper/TypeCheckerTest.php | 12 ++++++------ tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php | 4 ++-- 11 files changed, 45 insertions(+), 23 deletions(-) diff --git a/composer.json b/composer.json index 94c5298..1874d6d 100644 --- a/composer.json +++ b/composer.json @@ -63,7 +63,7 @@ "cs-fix": "phpcbf --standard=phpcs.xml Kununu/ tests/", "cs-fixer-check": "php-cs-fixer check --config=php-cs-fixer.php", "cs-fixer-fix": "php-cs-fixer fix --config=php-cs-fixer.php", - "rector": "rector process --dry-run Kununu/ tests/ -vvv", + "rector": "rector process --dry-run Kununu/ tests/", "rector-fix": "rector process Kununu/ tests/", "stan": "phpstan analyze", "test-unit": "phpunit" diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php index 6f85783..71b27a3 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/ArchitectureLibraryTest.php @@ -5,7 +5,6 @@ use InvalidArgumentException; use Kununu\ArchitectureSniffer\Configuration\ArchitectureLibrary; -use Kununu\ArchitectureSniffer\Configuration\Group; use PHPUnit\Framework\TestCase; final class ArchitectureLibraryTest extends TestCase diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php index 07df68c..c5d2192 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/GroupTest.php @@ -15,12 +15,12 @@ public function testBuildFromWithAllAttributes(): void groupName: 'TestGroup', flattenedIncludes: ['App\\Service\\'], targetAttributes: [ - Group::DEPENDS_ON_KEY => ['App\\Repository\\'], - Group::MUST_NOT_DEPEND_ON_KEY => ['App\\Controller\\'], - Group::EXTENDS_KEY => 'App\\Base\\AbstractService', - Group::IMPLEMENTS_KEY => ['App\\Contract\\ServiceInterface'], - Group::FINAL_KEY => true, - Group::READONLY_KEY => true, + Group::DEPENDS_ON_KEY => ['App\\Repository\\'], + Group::MUST_NOT_DEPEND_ON_KEY => ['App\\Controller\\'], + Group::EXTENDS_KEY => 'App\\Base\\AbstractService', + Group::IMPLEMENTS_KEY => ['App\\Contract\\ServiceInterface'], + Group::FINAL_KEY => true, + Group::READONLY_KEY => true, Group::MUST_ONLY_HAVE_ONE_PUBLIC_METHOD_NAMED_KEY => 'execute', ], flattenedExcludes: ['App\\Service\\Internal\\'], diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php index 209c725..1e1f1dd 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustBeFinalTest.php @@ -49,7 +49,11 @@ public function testCreateRuleWithExcludes(): void mustOnlyHaveOnePublicMethodName: null, ); $library = new ArchitectureLibrary([ - 'services' => ['includes' => ['App\\Service\\MyService'], 'excludes' => ['App\\Service\\Internal\\'], 'final' => true], + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'excludes' => ['App\\Service\\Internal\\'], + 'final' => true, + ], ]); $result = MustBeFinal::createRule($group, $library); diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php index fcddb28..c0a463a 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustExtendTest.php @@ -74,7 +74,10 @@ public function testCreateRuleThrowsWhenIncludesContainInterface(): void mustOnlyHaveOnePublicMethodName: null, ); $library = new ArchitectureLibrary([ - 'services' => ['includes' => ['App\\Contract\\ServiceInterface'], 'extends' => 'App\\Base\\AbstractService'], + 'services' => [ + 'includes' => ['App\\Contract\\ServiceInterface'], + 'extends' => 'App\\Base\\AbstractService', + ], ]); self::expectException(InvalidArgumentException::class); diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php index c6fdf30..207baf9 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustImplementTest.php @@ -28,7 +28,10 @@ public function testCreateRuleReturnsRule(): void mustOnlyHaveOnePublicMethodName: null, ); $library = new ArchitectureLibrary([ - 'services' => ['includes' => ['App\\Service\\MyService'], 'implements' => ['App\\Contract\\ServiceInterface']], + 'services' => [ + 'includes' => ['App\\Service\\MyService'], + 'implements' => ['App\\Contract\\ServiceInterface'], + ], ]); $result = MustImplement::createRule($group, $library); diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php index dc5f6f7..55b93d2 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodNamedTest.php @@ -27,7 +27,10 @@ public function testCreateRuleReturnsRule(): void mustOnlyHaveOnePublicMethodName: 'handle', ); $library = new ArchitectureLibrary([ - 'handlers' => ['includes' => ['App\\Handler\\MyHandler'], 'must_only_have_one_public_method_named' => 'handle'], + 'handlers' => [ + 'includes' => ['App\\Handler\\MyHandler'], + 'must_only_have_one_public_method_named' => 'handle', + ], ]); $result = MustOnlyHaveOnePublicMethodNamed::createRule($group, $library); diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php index 5fdebf9..5682520 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Configuration/Rules/MustOnlyHaveOnePublicMethodTest.php @@ -26,7 +26,10 @@ public function testCreateRuleReturnsRule(): void mustOnlyHaveOnePublicMethodName: 'handle', ); $library = new ArchitectureLibrary([ - 'handlers' => ['includes' => ['App\\Handler\\MyHandler'], 'must_only_have_one_public_method_named' => 'handle'], + 'handlers' => [ + 'includes' => ['App\\Handler\\MyHandler'], + 'must_only_have_one_public_method_named' => 'handle', + ], ]); $result = MustOnlyHaveOnePublicMethod::createRule($group, $library); diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php index dfe2418..c56e2a3 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Helper/GroupFlattenerTest.php @@ -23,7 +23,10 @@ public function testFlattenIncludesWithDirectFqcns(): void ], ]; - $result = GroupFlattener::flattenIncludes('services', ['App\\Service\\UserService', 'App\\Service\\OrderService']); + $result = GroupFlattener::flattenIncludes( + 'services', + ['App\\Service\\UserService', 'App\\Service\\OrderService'] + ); self::assertSame(['App\\Service\\UserService', 'App\\Service\\OrderService'], $result); } @@ -112,7 +115,11 @@ public function testFlattenExcludesReturnsNullWhenAllExcludesOverlapIncludes(): ], ]; - $result = GroupFlattener::flattenExcludes('services', ['App\\Service\\UserService'], ['App\\Service\\UserService']); + $result = GroupFlattener::flattenExcludes( + 'services', + ['App\\Service\\UserService'], + ['App\\Service\\UserService'] + ); self::assertNull($result); } diff --git a/tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php b/tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php index 3f89b9b..54c1947 100644 --- a/tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php +++ b/tests/Unit/Kununu/ArchitectureSniffer/Helper/TypeCheckerTest.php @@ -19,13 +19,13 @@ public function testIsArrayKeysOfStrings(mixed $input, bool $expected): void public static function isArrayKeysOfStringsProvider(): array { return [ - 'string keys' => [['a' => 1, 'b' => 2], true], - 'empty array' => [[], true], - 'integer keys' => [[1, 2, 3], false], - 'mixed keys' => [['a' => 1, 0 => 2], false], + 'string keys' => [['a' => 1, 'b' => 2], true], + 'empty array' => [[], true], + 'integer keys' => [[1, 2, 3], false], + 'mixed keys' => [['a' => 1, 0 => 2], false], 'not an array (string)' => ['hello', false], - 'not an array (int)' => [42, false], - 'not an array (null)' => [null, false], + 'not an array (int)' => [42, false], + 'not an array (null)' => [null, false], ]; } diff --git a/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php b/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php index ef6d1b6..bf827d2 100644 --- a/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php +++ b/tests/Unit/Kununu/CsFixer/CsFixerPluginTest.php @@ -9,8 +9,8 @@ use Composer\Script\ScriptEvents; use Kununu\CsFixer\CsFixerPlugin; use Kununu\CsFixer\Provider\CsFixerCommandProvider; -use PHPUnit\Framework\Attributes\WithoutErrorHandler; use PHPUnit\Framework\TestCase; +use Throwable; final class CsFixerPluginTest extends TestCase { @@ -77,7 +77,7 @@ public function testAddCsFixerGitHooksExecutesWithoutThrowing(): void ob_start(); try { $plugin->addCsFixerGitHooks(); - } catch (\Throwable) { + } catch (Throwable) { } ob_end_clean();