jaroslavtyc/drd-plus-tables

View on GitHub
tests/Tables/TablesTest.php

Summary

Maintainability
C
1 day
Test Coverage
<?php declare(strict_types = 1);

namespace DrdPlus\Tables;

use DrdPlus\Codes\Armaments\ArmamentCode;
use DrdPlus\Codes\Armaments\ArmorCode;
use DrdPlus\Codes\Armaments\ArrowCode;
use DrdPlus\Codes\Armaments\BodyArmorCode;
use DrdPlus\Codes\Armaments\DartCode;
use DrdPlus\Codes\Armaments\HelmCode;
use DrdPlus\Codes\Armaments\MeleeWeaponCode;
use DrdPlus\Codes\Armaments\MeleeWeaponlikeCode;
use DrdPlus\Codes\Armaments\ProjectileCode;
use DrdPlus\Codes\Armaments\ProtectiveArmamentCode;
use DrdPlus\Codes\Armaments\RangedWeaponCode;
use DrdPlus\Codes\Armaments\ShieldCode;
use DrdPlus\Codes\Armaments\SlingStoneCode;
use DrdPlus\Codes\Armaments\WeaponlikeCode;
use DrdPlus\Codes\Partials\AbstractCode;
use DrdPlus\Tables\Armaments\Armors\ArmorStrengthSanctionsTable;
use DrdPlus\Tables\Armaments\Armors\BodyArmorsTable;
use DrdPlus\Tables\Armaments\Armors\HelmsTable;
use DrdPlus\Tables\Armaments\Armors\ArmorWearingSkillTable;
use DrdPlus\Tables\Armaments\Projectiles\ArrowsTable;
use DrdPlus\Tables\Armaments\Projectiles\DartsTable;
use DrdPlus\Tables\Armaments\Projectiles\SlingStonesTable;
use DrdPlus\Tables\Armaments\Shields\ShieldUsageSkillTable;
use DrdPlus\Tables\Armaments\Shields\ShieldStrengthSanctionsTable;
use DrdPlus\Tables\Armaments\Shields\ShieldsTable;
use DrdPlus\Tables\Armaments\Weapons\Melee\MeleeWeaponStrengthSanctionsTable;
use DrdPlus\Tables\Armaments\Weapons\Melee\Partials\MeleeWeaponsTable;
use DrdPlus\Tables\Armaments\Weapons\Ranged\Partials\RangedWeaponsTable;
use DrdPlus\Tables\Armaments\Weapons\Ranged\RangedWeaponStrengthSanctionsTable;
use DrdPlus\Tests\Tables\TableTest;
use Granam\TestWithMockery\TestWithMockery;

class TablesTest extends TestWithMockery
{
    /**
     * @test
     * @throws \ReflectionException
     */
    public function I_can_get_any_table(): void
    {
        $reflectionClass = new \ReflectionClass(Tables::class);
        $tablesInstance = $reflectionClass->getProperty('tablesInstance');
        $tablesInstance->setAccessible(true);
        $tablesInstance->setValue(null);
        $tablesInstance->setAccessible(false);
        $tables = Tables::getIt();
        foreach ($this->getExpectedTableClasses() as $expectedTableClass) {
            $baseName = \preg_replace('~(?:.+[\\\])?(\w+)$~', '$1', $expectedTableClass);
            $getTable = "get{$baseName}";
            self::assertTrue(
                \method_exists($tables, $getTable),
                "'Tables' factory should has getter {$getTable} for {$expectedTableClass} (or the class should be abstract ?)"
            );
            $table = $tables->$getTable();
            self::assertInstanceOf($expectedTableClass, $table);
        }
    }

    /**
     * @test
     */
    public function I_can_iterate_through_tables(): void
    {
        $tables = Tables::getIt();
        $fetchedTableClasses = [];
        foreach ($tables as $table) {
            $fetchedTableClasses[] = \get_class($table);
        }
        $expectedTableClasses = $this->getExpectedTableClasses();
        \sort($expectedTableClasses);
        \sort($fetchedTableClasses);

        self::assertSameSize(
            $expectedTableClasses,
            $fetchedTableClasses,
            'Tables factory should give ' . \implode(',', \array_diff($expectedTableClasses, $fetchedTableClasses))
            . ' on iterating'
        );
        self::assertEquals($expectedTableClasses, $fetchedTableClasses);
    }

    private function getExpectedTableClasses(): array
    {
        /** @noinspection PhpUnhandledExceptionInspection */
        $tablesReflection = new \ReflectionClass(Tables::class);
        $rootDir = \dirname($tablesReflection->getFileName());
        $rootNamespace = $tablesReflection->getNamespaceName();

        return $this->scanForTables($rootDir, $rootNamespace);
    }

    private function scanForTables(string $rootDir, string $rootNamespace): array
    {
        $tableClasses = [];
        foreach (\scandir($rootDir, SCANDIR_SORT_NONE) as $folder) {
            $folderFullPath = $rootDir . DIRECTORY_SEPARATOR . $folder;
            if ($folder !== '.' && $folder !== '..') {
                if (\is_dir($folderFullPath)) {
                    foreach ($this->scanForTables($folderFullPath, $rootNamespace . '\\' . $folder) as $foundTable) {
                        $tableClasses[] = $foundTable;
                    }
                } elseif (\is_file($folderFullPath) && \preg_match('~(?<classBasename>\w+(?:Table)?)\.php$~', $folder, $matches)) {
                    /** @noinspection PhpUnhandledExceptionInspection */
                    $reflectionClass = new \ReflectionClass($rootNamespace . '\\' . $matches['classBasename']);
                    if ($reflectionClass->isInstantiable() && $reflectionClass->implementsInterface(Table::class)) {
                        self::assertMatchesRegularExpression(
                            '~Table$~',
                            $reflectionClass->getName(),
                            'Every single table should ends by "Table"'
                        );
                        $tableClasses[] = $reflectionClass->getName();
                    }
                }
            }
        }

        return $tableClasses;
    }

    /**
     * @test
     * @dataProvider provideArmamentCodeAndExpectedTableClass
     * @param ArmamentCode $armamentCode
     * @param string $expectedTableClass
     */
    public function I_can_get_every_armament_table_by_armament_code(ArmamentCode $armamentCode, $expectedTableClass): void
    {
        self::assertInstanceOf($expectedTableClass, Tables::getIt()->getArmamentsTableByArmamentCode($armamentCode));
    }

    /**
     * @return array
     * @throws \ReflectionException
     */
    public function provideArmamentCodeAndExpectedTableClass(): array
    {
        $values = [];
        foreach ([
                     BodyArmorCode::class => BodyArmorsTable::class,
                     HelmCode::class => HelmsTable::class,
                     ShieldCode::class => ShieldsTable::class,
                     MeleeWeaponCode::class => MeleeWeaponsTable::class,
                     RangedWeaponCode::class => RangedWeaponsTable::class,
                     ArrowCode::class => ArrowsTable::class,
                     DartCode::class => DartsTable::class,
                     SlingStoneCode::class => SlingStonesTable::class,
                 ] as $codeClass => $tableClass) {
            foreach ($this->pairCodesWithClass($this->getCodes($codeClass), $tableClass) as $pair) {
                $values[] = $pair;
            }
        }

        return $values;
    }

    /**
     * @param string $class
     * @return array
     * @throws \ReflectionException
     */
    private function getCodes(string $class): array
    {
        $codes = [];
        /** @var AbstractCode $class */
        $reflectionClass = new \ReflectionClass($class);
        foreach ($reflectionClass->getConstants() as $constant) {
            $codes[] = $class::getIt($constant);
        }

        return $codes;
    }

    /**
     * @param array $codes
     * @param $class
     * @return array
     */
    private function pairCodesWithClass(array $codes, string $class): array
    {
        return array_map(
            fn($code) => [$code, $class],
            $codes
        );
    }

    /**
     * @test
     */
    public function I_do_not_get_any_armament_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownArmament::class);
        /** @var ArmamentCode $armamentCode */
        $armamentCode = $this->mockery(ArmamentCode::class);
        Tables::getIt()->getArmamentsTableByArmamentCode($armamentCode);
    }

    /**
     * @test
     */
    public function I_do_not_get_any_weaponlike_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownWeaponlike::class);
        /** @var WeaponlikeCode $weaponlikeCode */
        $weaponlikeCode = $this->mockery(WeaponlikeCode::class);
        Tables::getIt()->getWeaponlikeTableByWeaponlikeCode($weaponlikeCode);
    }

    /**
     * @test
     */
    public function I_do_not_get_any_melee_weaponlike_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownMeleeWeaponlike::class);
        /** @var MeleeWeaponlikeCode $meleeWeaponlikeCode */
        $meleeWeaponlikeCode = $this->mockery(MeleeWeaponlikeCode::class);
        Tables::getIt()->getMeleeWeaponlikeTableByMeleeWeaponlikeCode($meleeWeaponlikeCode);
    }

    /**
     * @test
     */
    public function I_do_not_get_any_melee_weapons_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownMeleeWeapon::class);
        $this->expectExceptionMessageMatches('~denigration~');
        /** @var MeleeWeaponCode $meleeWeaponCode */
        $meleeWeaponCode = $this->createMeleeWeaponCode('denigration', 'poisonous language');
        Tables::getIt()->getMeleeWeaponsTableByMeleeWeaponCode($meleeWeaponCode);
    }

    /**
     * @param $value
     * @param string $matchingWeaponGroup
     * @return \Mockery\MockInterface|MeleeWeaponCode
     */
    private function createMeleeWeaponCode($value, $matchingWeaponGroup)
    {
        $code = $this->mockery(MeleeWeaponCode::class);
        $code->shouldReceive('getValue')
            ->andReturn($value);
        $code->shouldReceive('__toString')
            ->andReturn((string)$value);
        $weaponGroups = [
            'axe', 'knifeOrDagger', 'maceOrClub', 'morningstarOrMorgenstern',
            'saberOrBowieKnife', 'staffOrSpear', 'sword', 'unarmed', 'voulgeOrTrident',
        ];
        foreach ($weaponGroups as $weaponGroup) {
            $code->shouldReceive('is' . ucfirst($weaponGroup))
                ->andReturn($weaponGroup === $matchingWeaponGroup);
        }

        return $code;
    }

    /**
     * @test
     * @dataProvider provideArmamentCodeAndExpectedSanctionsTable
     * @param ArmamentCode $armamentCode
     * @param string $expectedTableClass
     */
    public function I_can_get_table_with_sanctions_by_missing_strength_for_every_armament(
        ArmamentCode $armamentCode,
        string $expectedTableClass
    ): void
    {
        self::assertInstanceOf(
            $expectedTableClass,
            Tables::getIt()->getArmamentStrengthSanctionsTableByCode($armamentCode)
        );
    }

    public function provideArmamentCodeAndExpectedSanctionsTable(): array
    {
        return [
            [BodyArmorCode::getIt(BodyArmorCode::HOBNAILED_ARMOR), ArmorStrengthSanctionsTable::class],
            [HelmCode::getIt(HelmCode::GREAT_HELM), ArmorStrengthSanctionsTable::class],
            [RangedWeaponCode::getIt(RangedWeaponCode::HEAVY_CROSSBOW), RangedWeaponStrengthSanctionsTable::class],
            [MeleeWeaponCode::getIt(MeleeWeaponCode::CLUB), MeleeWeaponStrengthSanctionsTable::class],
            [ShieldCode::getIt(ShieldCode::BUCKLER), ShieldStrengthSanctionsTable::class],
        ];
    }

    /**
     * @test
     */
    public function I_do_not_get_any_sanctions_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownArmament::class);
        /** @var ArmorCode $armamentCode */
        $armamentCode = $this->mockery(ArmamentCode::class);
        Tables::getIt()->getArmamentStrengthSanctionsTableByCode($armamentCode);
    }

    /**
     * @test
     */
    public function I_do_not_get_any_weaponlike_sanctions_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownWeaponlike::class);
        /** @var WeaponlikeCode $weaponlikeCode */
        $weaponlikeCode = $this->mockery(WeaponlikeCode::class);
        Tables::getIt()->getWeaponlikeStrengthSanctionsTableByCode($weaponlikeCode);
    }

    /**
     * @test
     */
    public function I_do_not_get_any_melee_weaponlike_sanctions_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownMeleeWeaponlike::class);
        /** @var MeleeWeaponlikeCode $meleeWeaponlikeCode */
        $meleeWeaponlikeCode = $this->mockery(MeleeWeaponlikeCode::class);
        Tables::getIt()->getMeleeWeaponlikeStrengthSanctionsTableByCode($meleeWeaponlikeCode);
    }

    /**
     * @test
     * @throws \ReflectionException
     */
    public function I_do_not_get_range_weapons_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownRangedWeapon::class);
        $this->expectExceptionMessageMatches('~wallop~');
        /** @var RangedWeaponCode $rangeWeaponCode */
        $rangeWeaponCode = $this->createRangedWeaponCode('wallop', 'bio weapons');
        Tables::getIt()->getRangedWeaponsTableByRangedWeaponCode($rangeWeaponCode);
    }

    /**
     * @param $value
     * @param string $matchingWeaponGroup
     * @return \Mockery\MockInterface|RangedWeaponCode
     * @throws \ReflectionException
     */
    private function createRangedWeaponCode(string $value, string $matchingWeaponGroup)
    {
        $code = $this->mockery(RangedWeaponCode::class);
        $code->shouldReceive('getValue')
            ->andReturn($value);
        $code->shouldReceive('__toString')
            ->andReturn($value);
        $rangeWeaponGroups = ['bow', 'arrow', 'crossbow', 'dart', 'throwingWeapon', 'slingStone'];
        $codeReflection = new \ReflectionClass(RangedWeaponCode::class);
        foreach ($rangeWeaponGroups as $weaponGroup) {
            $isType = 'is' . \ucfirst($weaponGroup);
            if ($codeReflection->hasMethod($isType)) {
                $code->shouldReceive('is' . \ucfirst($weaponGroup))
                    ->andReturn($weaponGroup === $matchingWeaponGroup);
            }
        }

        return $code;
    }

    /**
     * @test
     */
    public function I_do_not_get_any_armors_table_by_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownArmor::class);
        /** @var ArmorCode $armorCode */
        $armorCode = $this->mockery(ArmorCode::class);
        Tables::getIt()->getArmorsTableByArmorCode($armorCode);
    }

    /**
     * @test
     * @dataProvider provideProtectiveArmamentCodeAndExpectedSanctionsTable
     * @param ProtectiveArmamentCode $protectiveArmamentCode
     * @param string $expectedTableClass
     */
    public function I_can_get_table_with_sanctions_by_missing_skill_for_every_protective_armament(
        ProtectiveArmamentCode $protectiveArmamentCode,
        string $expectedTableClass
    ): void
    {
        self::assertInstanceOf(
            $expectedTableClass,
            Tables::getIt()->getProtectiveArmamentMissingSkillTableByCode($protectiveArmamentCode)
        );
    }

    public function provideProtectiveArmamentCodeAndExpectedSanctionsTable(): array
    {
        return [
            [BodyArmorCode::getIt(BodyArmorCode::HOBNAILED_ARMOR), ArmorWearingSkillTable::class],
            [HelmCode::getIt(HelmCode::GREAT_HELM), ArmorWearingSkillTable::class],
            [ShieldCode::getIt(ShieldCode::BUCKLER), ShieldUsageSkillTable::class],
        ];
    }

    /**
     * @test
     */
    public function I_do_not_get_table_any_sanctions_by_missing_skill_table_for_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownProtectiveArmament::class);
        /** @var ProtectiveArmamentCode $protectiveArmamentCode */
        $protectiveArmamentCode = $this->mockery(ProtectiveArmamentCode::class);
        Tables::getIt()->getProtectiveArmamentMissingSkillTableByCode($protectiveArmamentCode);
    }

    /**
     * @test
     * @dataProvider provideProtectiveArmamentCodeAndExpectedRestrictionTable
     * @param ProtectiveArmamentCode $protectiveArmamentCode
     * @param string $expectedTableClass
     */
    public function I_can_get_table_with_restriction_for_every_protective_armament(
        ProtectiveArmamentCode $protectiveArmamentCode,
        string $expectedTableClass
    ): void
    {
        self::assertInstanceOf(
            $expectedTableClass,
            Tables::getIt()->getProtectiveArmamentsTable($protectiveArmamentCode)
        );
    }

    public function provideProtectiveArmamentCodeAndExpectedRestrictionTable(): array
    {
        return [
            [BodyArmorCode::getIt(BodyArmorCode::HOBNAILED_ARMOR), BodyArmorsTable::class],
            [HelmCode::getIt(HelmCode::GREAT_HELM), HelmsTable::class],
            [ShieldCode::getIt(ShieldCode::BUCKLER), ShieldsTable::class],
        ];
    }

    /**
     * @test
     */
    public function I_do_not_get_table_any_restriction_table_for_unknown_code(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownProtectiveArmament::class);
        /** @var ProtectiveArmamentCode $protectiveArmamentCode */
        $protectiveArmamentCode = $this->mockery(ProtectiveArmamentCode::class);
        Tables::getIt()->getProtectiveArmamentsTable($protectiveArmamentCode);
    }

    /**
     * @test
     */
    public function Every_table_is_tested_by_default_test(): void
    {
        foreach ($this->getExpectedTableClasses() as $expectedTableClass) {
            $expectedTableTestClass = str_replace('\Tables\\', '\Tests\Tables\\', $expectedTableClass) . 'Test';
            self::assertTrue(class_exists($expectedTableTestClass), 'Missing test for table ' . $expectedTableClass);
            self::assertTrue(
                is_a($expectedTableTestClass, TableTest::class, true),
                "Table test {$expectedTableTestClass} should extends " . TableTest::class
            );
        }
    }

    /**
     * @test
     */
    public function I_can_not_get_projectiles_table_for_unknown_projectile(): void
    {
        $this->expectException(\DrdPlus\Tables\Armaments\Exceptions\UnknownProjectile::class);
        $this->expectExceptionMessageMatches('~foo~');
        $projectile = $this->mockery(ProjectileCode::class);
        $projectile->shouldReceive('isArrow')->andReturn(false);
        $projectile->shouldReceive('isDart')->andReturn(false);
        $projectile->shouldReceive('isSlingStone')->andReturn(false);
        $projectile->shouldReceive('__toString')->andReturn('foo');
        /** @var ProjectileCode $projectile */
        Tables::getIt()->getProjectilesTableByProjectiveCode($projectile);
    }
}