Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
171 changes: 171 additions & 0 deletions tests/Unit/Kununu/ArchitectureSniffer/ArchitectureSnifferTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
<?php
declare(strict_types=1);

namespace Tests\Unit\Kununu\ArchitectureSniffer;

use InvalidArgumentException;
use Kununu\ArchitectureSniffer\ArchitectureSniffer;
use Kununu\ArchitectureSniffer\Helper\ProjectPathResolver;
use PHPat\Test\Builder\Rule;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Yaml\Yaml;

final class ArchitectureSnifferTest extends TestCase
{
private string $architectureFile;
private string $architectureDir;

protected function setUp(): void
{
$this->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));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);

namespace Tests\Unit\Kununu\ArchitectureSniffer\Configuration;

use InvalidArgumentException;
use Kununu\ArchitectureSniffer\Configuration\ArchitectureLibrary;
use PHPUnit\Framework\TestCase;

final class ArchitectureLibraryTest extends TestCase
{
public function testConstructorBuildsGroupsSuccessfully(): void
{
$library = $this->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'],
],
]);
}
}
Loading
Loading