diff --git a/config/services.php b/config/services.php index d1faa39a..ee793e51 100644 --- a/config/services.php +++ b/config/services.php @@ -26,10 +26,10 @@ use Cocur\Slugify; use CPSIT\ProjectBuilder\Builder; use CPSIT\ProjectBuilder\IO; -use CPSIT\ProjectBuilder\Template; use CPSIT\ProjectBuilder\Twig; use GuzzleHttp\Client as GuzzleClient; use Nyholm\Psr7; +use Opis\JsonSchema; use Psr\Http\Client; use Psr\Http\Message; use SebastianFeldmann\Cli; @@ -43,6 +43,9 @@ DependencyInjection\Loader\Configurator\ContainerConfigurator $configurator, DependencyInjection\ContainerBuilder $container, ): void { + $container->registerForAutoconfiguration(Builder\Artifact\Migration\Migration::class) + ->addTag('artifact.migration') + ; $container->registerForAutoconfiguration(Builder\Writer\WriterInterface::class) ->addTag('builder.writer') ; @@ -55,9 +58,6 @@ $container->registerForAutoconfiguration(IO\Validator\ValidatorInterface::class) ->addTag('io.validator') ; - $container->registerForAutoconfiguration(Template\Provider\ProviderInterface::class) - ->addTag('template.provider') - ; $container->registerForAutoconfiguration(Twig\Filter\TwigFilterInterface::class) ->addTag('twig.filter') ; @@ -84,6 +84,7 @@ // Add external services $services->set(ExpressionLanguage\ExpressionLanguage::class); $services->set(Filesystem\Filesystem::class); + $services->set(JsonSchema\Validator::class); $services->set(Slugify\Slugify::class); $services->set(Client\ClientInterface::class, GuzzleClient::class); $services->set(Loader\LoaderInterface::class, Loader\FilesystemLoader::class); diff --git a/config/services.yaml b/config/services.yaml index caca8c82..1f132d53 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -7,7 +7,7 @@ services: CPSIT\ProjectBuilder\: resource: '../src/*' exclude: - - '../src/Builder/Artifact/*' + - '../src/Builder/Artifact/*.php' - '../src/Builder/Config/*' - '../src/Builder/Generator/Step/CleanUpStep.php' - '../src/Builder/Generator/Step/DumpBuildArtifactStep.php' @@ -21,6 +21,10 @@ services: - '../src/Resource/Local/ProcessedFile.php' - '../src/Template/TemplateSource.php' + CPSIT\ProjectBuilder\Builder\ArtifactReader: + arguments: + $migrations: !tagged_iterator artifact.migration + CPSIT\ProjectBuilder\Builder\Config\Config: alias: 'app.config' @@ -45,10 +49,6 @@ services: CPSIT\ProjectBuilder\IO\Messenger: alias: 'app.messenger' - CPSIT\ProjectBuilder\Template\Provider\ProviderFactory: - arguments: - $providers: !tagged_iterator template.provider - CPSIT\ProjectBuilder\Twig\Extension\ProjectBuilderExtension: arguments: $filters: !tagged_iterator twig.filter diff --git a/docs/development/architecture/components.md b/docs/development/architecture/components.md index c14b4ee3..9f96cb93 100644 --- a/docs/development/architecture/components.md +++ b/docs/development/architecture/components.md @@ -70,6 +70,24 @@ Each validator implements [`ValidatorInterface`](https://github.com/CPS-IT/proje Not all validators can be used for each interaction with the `InputReader`. ``` +## JSON schema validation + +While working with JSON files, it's often useful to validate them against a defined +JSON schema. For this, the [**`Json\SchemaValidator`**](https://github.com/CPS-IT/project-builder/blob/main/src/Json/SchemaValidator.php) +component can be used. + +Example: + +```php +/** @var \CPSIT\ProjectBuilder\Json\SchemaValidator $schemaValidator */ + +$data = json_decode($json); +$validationResult = $schemaValidator->validate($data, $schemaFile); + +$isValid = $validationResult->isValid(); // Check if JSON is valid +$error = $validationResult->error(); // Get validation errors +``` + ## Naming With the [**`Naming\NameVariantBuilder`**](https://github.com/CPS-IT/project-builder/blob/main/src/Naming/NameVariantBuilder.php) diff --git a/rector.php b/rector.php index 2ec3afc7..d0937756 100644 --- a/rector.php +++ b/rector.php @@ -23,6 +23,7 @@ use Rector\Config\RectorConfig; use Rector\Core\ValueObject\PhpVersion; +use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector; use Rector\Php74\Rector\LNumber\AddLiteralSeparatorToNumberRector; use Rector\Php80\Rector\Class_\AnnotationToAttributeRector; use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector; @@ -51,6 +52,10 @@ __DIR__.'/src/DependencyInjection/ContainerFactory.php', __DIR__.'/src/ProjectBuilderPlugin.php', ], + JsonThrowOnErrorRector::class => [ + __DIR__.'/src/Builder/ArtifactGenerator.php', + __DIR__.'/src/Builder/ArtifactReader.php', + ], NullToStrictStringFuncCallArgRector::class => [ __DIR__.'/src/Builder/Generator/Step/ProcessingFilesTrait.php', ], diff --git a/resources/build-artifact.schema.json b/resources/build-artifact.schema.json index 20266602..ad15dcb4 100644 --- a/resources/build-artifact.schema.json +++ b/resources/build-artifact.schema.json @@ -1,5 +1,5 @@ { - "$schema": "https://json-schema.org/draft-04/schema", + "$schema": "https://json-schema.org/draft/2019-09/schema#", "type": "object", "title": "Build artifact for projects generated with the Project Builder", "properties": { diff --git a/src/Builder/Artifact/Artifact.php b/src/Builder/Artifact/Artifact.php index bd8849fd..c6429181 100644 --- a/src/Builder/Artifact/Artifact.php +++ b/src/Builder/Artifact/Artifact.php @@ -33,17 +33,38 @@ * * @internal * - * @template T of array + * @phpstan-type ArtifactType array{ + * artifact: BuildArtifact, + * template: TemplateArtifact, + * generator: GeneratorArtifact, + * result: ResultArtifact + * } */ -abstract class Artifact implements JsonSerializable +final class Artifact implements JsonSerializable { + public function __construct( + public readonly BuildArtifact $artifact, + public readonly TemplateArtifact $template, + public readonly GeneratorArtifact $generator, + public readonly ResultArtifact $result, + ) { + } + /** - * @return T + * @phpstan-return ArtifactType */ - abstract public function dump(): array; + public function dump(): array + { + return [ + 'artifact' => $this->artifact, + 'template' => $this->template, + 'generator' => $this->generator, + 'result' => $this->result, + ]; + } /** - * @return T + * @phpstan-return ArtifactType */ public function jsonSerialize(): array { diff --git a/src/Builder/Artifact/BuildArtifact.php b/src/Builder/Artifact/BuildArtifact.php index 980557cb..7ee19b41 100644 --- a/src/Builder/Artifact/BuildArtifact.php +++ b/src/Builder/Artifact/BuildArtifact.php @@ -23,12 +23,7 @@ namespace CPSIT\ProjectBuilder\Builder\Artifact; -use Composer\Package; -use CPSIT\ProjectBuilder\Builder; -use CPSIT\ProjectBuilder\Helper; -use Symfony\Component\Finder; - -use function time; +use JsonSerializable; /** * BuildArtifact. @@ -37,41 +32,29 @@ * @license GPL-3.0-or-later * * @internal - * - * @extends Artifact */ -final class BuildArtifact extends Artifact +final class BuildArtifact implements JsonSerializable { - private const VERSION = 1; - public function __construct( - private readonly string $file, - private readonly Builder\BuildResult $buildResult, - private readonly Package\RootPackageInterface $rootPackage, + public readonly int $version, + public readonly string $path, + public readonly int $date, ) { } - public function dump(): array + /** + * @return array{ + * version: int, + * path: string, + * date: int, + * } + */ + public function jsonSerialize(): array { return [ - 'artifact' => [ - 'version' => self::VERSION, - 'file' => $this->file, - 'date' => time(), - ], - 'template' => new TemplateArtifact($this->buildResult), - 'generator' => new GeneratorArtifact($this->rootPackage), - 'result' => new ResultArtifact($this->buildResult), + 'version' => $this->version, + 'path' => $this->path, + 'date' => $this->date, ]; } - - public function getFile(): Finder\SplFileInfo - { - return Helper\FilesystemHelper::createFileObject($this->buildResult->getWrittenDirectory(), $this->file); - } } diff --git a/src/Builder/Artifact/GeneratorArtifact.php b/src/Builder/Artifact/GeneratorArtifact.php index dd7cd519..755b9bef 100644 --- a/src/Builder/Artifact/GeneratorArtifact.php +++ b/src/Builder/Artifact/GeneratorArtifact.php @@ -23,7 +23,7 @@ namespace CPSIT\ProjectBuilder\Builder\Artifact; -use Composer\Package; +use JsonSerializable; /** * GeneratorArtifact. @@ -32,32 +32,26 @@ * @license GPL-3.0-or-later * * @internal - * - * @extends Artifact */ -final class GeneratorArtifact extends Artifact +final class GeneratorArtifact implements JsonSerializable { public function __construct( - private readonly Package\RootPackageInterface $rootPackage, + public readonly PackageArtifact $package, + public readonly string $executor, ) { } - public function dump(): array + /** + * @return array{ + * package: PackageArtifact, + * executor: string, + * } + */ + public function jsonSerialize(): array { return [ - 'package' => new PackageArtifact($this->rootPackage), - 'executor' => $this->determineExecutor(), + 'package' => $this->package, + 'executor' => $this->executor, ]; } - - private function determineExecutor(): string - { - return match (getenv('PROJECT_BUILDER_EXECUTOR')) { - 'docker' => 'docker', - default => 'composer', - }; - } } diff --git a/src/Builder/Artifact/Migration/BaseMigration.php b/src/Builder/Artifact/Migration/BaseMigration.php new file mode 100644 index 00000000..989d2efe --- /dev/null +++ b/src/Builder/Artifact/Migration/BaseMigration.php @@ -0,0 +1,64 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Builder\Artifact\Migration; + +use CPSIT\ProjectBuilder\Helper; + +use function is_callable; + +/** + * BaseMigration. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +abstract class BaseMigration implements Migration +{ + /** + * @param array $artifact + * @param non-empty-string $path + * @param non-empty-string|null $targetPath + */ + protected function remapValue( + array &$artifact, + string $path, + string $targetPath = null, + mixed $newValue = null, + ): void { + $currentValue = Helper\ArrayHelper::getValueByPath($artifact, $path); + + if (is_callable($newValue)) { + $newValue = $newValue($currentValue); + } elseif (null === $newValue) { + $newValue = $currentValue; + } + + if (null === $targetPath) { + Helper\ArrayHelper::setValueByPath($artifact, $path, $newValue); + } else { + Helper\ArrayHelper::setValueByPath($artifact, $targetPath, $newValue); + Helper\ArrayHelper::removeByPath($artifact, $path); + } + } +} diff --git a/src/Builder/Artifact/Migration/Migration.php b/src/Builder/Artifact/Migration/Migration.php new file mode 100644 index 00000000..d1f6473b --- /dev/null +++ b/src/Builder/Artifact/Migration/Migration.php @@ -0,0 +1,46 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Builder\Artifact\Migration; + +/** + * Migration. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +interface Migration +{ + /** + * @param array $artifact + * + * @return array + */ + public function migrate(array $artifact): array; + + public static function getSourceVersion(): int; + + public static function getTargetVersion(): int; +} diff --git a/src/Builder/Artifact/Migration/Migration1679497137.php b/src/Builder/Artifact/Migration/Migration1679497137.php new file mode 100644 index 00000000..652d7990 --- /dev/null +++ b/src/Builder/Artifact/Migration/Migration1679497137.php @@ -0,0 +1,56 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Builder\Artifact\Migration; + +/** + * Migration1679497137. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class Migration1679497137 extends BaseMigration +{ + public function migrate(array $artifact): array + { + // Silent migration from artifact.file to artifact.path + // since this was wrong implemented in the first place + $this->remapValue( + $artifact, + 'artifact.file', + 'artifact.path', + ); + + return $artifact; + } + + public static function getSourceVersion(): int + { + return 1; + } + + public static function getTargetVersion(): int + { + return 1; + } +} diff --git a/src/Builder/Artifact/PackageArtifact.php b/src/Builder/Artifact/PackageArtifact.php index b84ab7dd..0f8f882d 100644 --- a/src/Builder/Artifact/PackageArtifact.php +++ b/src/Builder/Artifact/PackageArtifact.php @@ -23,7 +23,7 @@ namespace CPSIT\ProjectBuilder\Builder\Artifact; -use Composer\Package; +use JsonSerializable; /** * PackageArtifact. @@ -32,30 +32,35 @@ * @license GPL-3.0-or-later * * @internal - * - * @extends Artifact */ -final class PackageArtifact extends Artifact +final class PackageArtifact implements JsonSerializable { public function __construct( - private readonly Package\PackageInterface $package, + public readonly string $name, + public readonly string $version, + public readonly ?string $sourceReference, + public readonly ?string $sourceUrl, + public readonly ?string $distUrl, ) { } - public function dump(): array + /** + * @return array{ + * name: string, + * version: string, + * sourceReference: string|null, + * sourceUrl: string|null, + * distUrl: string|null, + * } + */ + public function jsonSerialize(): array { return [ - 'name' => $this->package->getName(), - 'version' => $this->package->getVersion(), - 'sourceReference' => $this->package->getSourceReference(), - 'sourceUrl' => $this->package->getSourceUrl(), - 'distUrl' => $this->package->getDistUrl(), + 'name' => $this->name, + 'version' => $this->version, + 'sourceReference' => $this->sourceReference, + 'sourceUrl' => $this->sourceUrl, + 'distUrl' => $this->distUrl, ]; } } diff --git a/src/Builder/Artifact/ResultArtifact.php b/src/Builder/Artifact/ResultArtifact.php index 8d7abd33..ce35e8a6 100644 --- a/src/Builder/Artifact/ResultArtifact.php +++ b/src/Builder/Artifact/ResultArtifact.php @@ -23,8 +23,8 @@ namespace CPSIT\ProjectBuilder\Builder\Artifact; -use CPSIT\ProjectBuilder\Builder; -use CPSIT\ProjectBuilder\Resource; +use JsonSerializable; +use stdClass; /** * ResultArtifact. @@ -33,38 +33,34 @@ * @license GPL-3.0-or-later * * @internal - * - * @extends Artifact, - * steps: list, - * processedFiles: list - * }> */ -final class ResultArtifact extends Artifact +final class ResultArtifact implements JsonSerializable { + /** + * @param array $properties + * @param list $steps + * @param list $processedFiles + */ public function __construct( - private readonly Builder\BuildResult $buildResult, + public readonly array $properties, + public readonly array $steps, + public readonly array $processedFiles, ) { } - public function dump(): array + /** + * @return array{ + * properties: stdClass, + * steps: list, + * processedFiles: list, + * } + */ + public function jsonSerialize(): array { return [ - 'properties' => $this->buildResult->getInstructions()->getTemplateVariables(), - 'steps' => array_map( - fn (Builder\Config\ValueObject\Step $step) => [ - 'type' => $step->getType(), - 'applied' => $this->buildResult->isStepApplied($step->getType()), - ], - $this->buildResult->getInstructions()->getConfig()->getSteps(), - ), - 'processedFiles' => array_map( - fn (Resource\Local\ProcessedFile $processedFile) => [ - 'source' => $processedFile->getOriginalFile()->getRelativePathname(), - 'target' => $processedFile->getTargetFile()->getRelativePathname(), - ], - $this->buildResult->getProcessedFiles(), - ), + 'properties' => (object) $this->properties, + 'steps' => $this->steps, + 'processedFiles' => $this->processedFiles, ]; } } diff --git a/src/Builder/Artifact/TemplateArtifact.php b/src/Builder/Artifact/TemplateArtifact.php index 4f1a5936..eb075103 100644 --- a/src/Builder/Artifact/TemplateArtifact.php +++ b/src/Builder/Artifact/TemplateArtifact.php @@ -23,7 +23,7 @@ namespace CPSIT\ProjectBuilder\Builder\Artifact; -use CPSIT\ProjectBuilder\Builder\BuildResult; +use JsonSerializable; /** * TemplateArtifact. @@ -32,35 +32,35 @@ * @license GPL-3.0-or-later * * @internal - * - * @extends Artifact */ -final class TemplateArtifact extends Artifact +final class TemplateArtifact implements JsonSerializable { + /** + * @param array{name: string, url: string} $provider + */ public function __construct( - private readonly BuildResult $buildResult, + public readonly string $identifier, + public readonly string $hash, + public readonly PackageArtifact $package, + public readonly array $provider, ) { } - public function dump(): array + /** + * @return array{ + * identifier: string, + * hash: string, + * package: PackageArtifact, + * provider: array{name: string, url: string}, + * } + */ + public function jsonSerialize(): array { - $config = $this->buildResult->getInstructions()->getConfig(); - $package = $config->getTemplateSource()->getPackage(); - $provider = $config->getTemplateSource()->getProvider(); - return [ - 'identifier' => $config->getIdentifier(), - 'hash' => $config->buildHash(), - 'package' => new PackageArtifact($package), - 'provider' => [ - 'name' => $provider::getName(), - 'url' => $provider->getUrl(), - ], + 'identifier' => $this->identifier, + 'hash' => $this->hash, + 'package' => $this->package, + 'provider' => $this->provider, ]; } } diff --git a/src/Builder/ArtifactGenerator.php b/src/Builder/ArtifactGenerator.php new file mode 100644 index 00000000..681ffa5f --- /dev/null +++ b/src/Builder/ArtifactGenerator.php @@ -0,0 +1,153 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Builder; + +use Composer\Package; +use CPSIT\ProjectBuilder\Resource; +use Symfony\Component\Finder; + +use function array_map; +use function getenv; + +/** + * ArtifactGenerator. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +final class ArtifactGenerator +{ + public const VERSION = 1; + + public function build( + Finder\SplFileInfo $file, + BuildResult $buildResult, + Package\RootPackageInterface $rootPackage, + int $version = self::VERSION, + ): Artifact\Artifact { + return new Artifact\Artifact( + $this->generateBuildArtifact($file, $version), + $this->generateTemplateArtifact($buildResult), + $this->generateGeneratorArtifact($rootPackage), + $this->generateResultArtifact($buildResult), + ); + } + + private function generateBuildArtifact(Finder\SplFileInfo $file, int $version): Artifact\BuildArtifact + { + return new Artifact\BuildArtifact( + $version, + $file->getRelativePathname(), + time(), + ); + } + + private function generateTemplateArtifact(BuildResult $buildResult): Artifact\TemplateArtifact + { + $config = $buildResult->getInstructions()->getConfig(); + $package = $config->getTemplateSource()->getPackage(); + $provider = $config->getTemplateSource()->getProvider(); + + $providerArtifact = [ + 'name' => $provider::getName(), + 'url' => $provider->getUrl(), + ]; + + return new Artifact\TemplateArtifact( + $config->getIdentifier(), + $config->buildHash(), + $this->generatePackageArtifact($package), + $providerArtifact, + ); + } + + private function generateGeneratorArtifact(Package\RootPackageInterface $rootPackage): Artifact\GeneratorArtifact + { + return new Artifact\GeneratorArtifact( + $this->generatePackageArtifact($rootPackage), + $this->determineExecutor(), + ); + } + + private function generateResultArtifact(BuildResult $buildResult): Artifact\ResultArtifact + { + $steps = array_map( + fn (Config\ValueObject\Step $step) => $this->mapStep($step, $buildResult), + $buildResult->getInstructions()->getConfig()->getSteps(), + ); + $processedFiles = array_map( + $this->mapProcessedFile(...), + $buildResult->getProcessedFiles(), + ); + + return new Artifact\ResultArtifact( + $buildResult->getInstructions()->getTemplateVariables(), + $steps, + $processedFiles, + ); + } + + /** + * @return array{type: string, applied: bool} + */ + private function mapStep(Config\ValueObject\Step $step, BuildResult $buildResult): array + { + return [ + 'type' => $step->getType(), + 'applied' => $buildResult->isStepApplied($step->getType()), + ]; + } + + /** + * @return array{source: string, target: string} + */ + private function mapProcessedFile(Resource\Local\ProcessedFile $processedFile): array + { + return [ + 'source' => $processedFile->getOriginalFile()->getRelativePathname(), + 'target' => $processedFile->getTargetFile()->getRelativePathname(), + ]; + } + + private function generatePackageArtifact(Package\PackageInterface $package): Artifact\PackageArtifact + { + return new Artifact\PackageArtifact( + $package->getName(), + $package->getPrettyVersion(), + $package->getSourceReference(), + $package->getSourceUrl(), + $package->getDistUrl(), + ); + } + + private function determineExecutor(): string + { + return match (getenv('PROJECT_BUILDER_EXECUTOR')) { + 'docker' => 'docker', + default => 'composer', + }; + } +} diff --git a/src/Builder/ArtifactReader.php b/src/Builder/ArtifactReader.php new file mode 100644 index 00000000..7c8658d9 --- /dev/null +++ b/src/Builder/ArtifactReader.php @@ -0,0 +1,202 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Builder; + +use CPSIT\ProjectBuilder\Exception; +use CPSIT\ProjectBuilder\Helper; +use CPSIT\ProjectBuilder\Json; +use CPSIT\ProjectBuilder\Paths; +use CuyZ\Valinor; +use DateTimeInterface; +use Opis\JsonSchema; +use ReflectionObject; +use Symfony\Component\Filesystem; + +use function array_filter; +use function array_values; +use function file_get_contents; +use function implode; +use function is_array; +use function is_numeric; +use function json_decode; +use function ksort; +use function range; + +/** + * ArtifactReader. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ArtifactReader +{ + private readonly Valinor\Mapper\TreeMapper $mapper; + + /** + * @var list + */ + private readonly array $migrations; + + /** + * @param iterable $migrations + */ + public function __construct( + iterable $migrations, + private readonly Filesystem\Filesystem $filesystem, + private readonly Json\SchemaValidator $schemaValidator, + ) { + $this->mapper = $this->createMapper(); + $this->migrations = $this->orderMigrations($migrations); + } + + /** + * @throws Exception\InvalidArtifactException + * @throws Valinor\Mapper\MappingError + */ + public function fromFile(string $file): Artifact\Artifact + { + $artifact = $this->parseArtifactFile($file); + $migratedArtifact = $this->performMigrations($artifact); + $validationResult = $this->schemaValidator->validate( + JsonSchema\Helper::toJSON($migratedArtifact), + Paths::BUILD_ARTIFACT_SCHEMA, + ); + + // Validate migrated artifact + if (!$validationResult->isValid()) { + throw Exception\InvalidArtifactException::forValidationErrors($validationResult->error()); + } + + // Create mapper source from artifact file + $source = Valinor\Mapper\Source\Source::array($migratedArtifact); + + return $this->mapper->map(Artifact\Artifact::class, $source); + } + + /** + * @return array + * + * @throws Exception\InvalidArtifactException + */ + private function parseArtifactFile(string $file): array + { + if (!$this->filesystem->exists($file)) { + throw Exception\InvalidArtifactException::forFile($file); + } + + $content = file_get_contents($file); + + // @codeCoverageIgnoreStart + if (false === $content) { + throw Exception\InvalidArtifactException::forFile($file); + } + // @codeCoverageIgnoreEnd + + $artifact = json_decode($content, true); + + // Assure artifact is an array + if (!is_array($artifact)) { + throw Exception\InvalidArtifactException::forFile($file); + } + + // Assure artifact is an associative array + if ($artifact !== array_filter($artifact, 'is_string', ARRAY_FILTER_USE_KEY)) { + throw Exception\InvalidArtifactException::forFile($file); + } + + return $artifact; + } + + /** + * @param array $artifact + * + * @return array + * + * @throws Exception\InvalidArtifactException + */ + private function performMigrations(array $artifact): array + { + $artifactVersion = $this->determineArtifactVersion($artifact); + $migrationPath = range($artifactVersion, ArtifactGenerator::VERSION); + + foreach ($migrationPath as $sourceVersion) { + foreach ($this->migrations as $migration) { + if ($sourceVersion === $migration::getSourceVersion()) { + $artifact = $migration->migrate($artifact); + } + } + } + + return $artifact; + } + + /** + * @param array $artifact + * + * @throws Exception\InvalidArtifactException + */ + private function determineArtifactVersion(array $artifact): int + { + $version = Helper\ArrayHelper::getValueByPath($artifact, 'artifact.version'); + + if (!is_numeric($version)) { + throw Exception\InvalidArtifactException::forInvalidVersion(); + } + + return (int) $version; + } + + private function createMapper(): Valinor\Mapper\TreeMapper + { + return (new Valinor\MapperBuilder()) + ->allowPermissiveTypes() + ->supportDateFormats(DateTimeInterface::ATOM) + ->mapper() + ; + } + + /** + * @param iterable $migrations + * + * @return list + */ + private function orderMigrations(iterable $migrations): array + { + $prefixedMigrations = []; + + foreach ($migrations as $migration) { + $reflectionObject = new ReflectionObject($migration); + $migrationIdentifier = implode('_', [ + $migration::getSourceVersion(), + $migration::getTargetVersion(), + $reflectionObject->getShortName(), + ]); + $prefixedMigrations[$migrationIdentifier] = $migration; + } + + ksort($prefixedMigrations); + + return array_values($prefixedMigrations); + } +} diff --git a/src/Builder/BuildResult.php b/src/Builder/BuildResult.php index 6f9ec996..6a9296ca 100644 --- a/src/Builder/BuildResult.php +++ b/src/Builder/BuildResult.php @@ -25,6 +25,7 @@ use CPSIT\ProjectBuilder\Resource; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; use function array_values; @@ -37,7 +38,7 @@ final class BuildResult { private bool $mirrored = false; - private ?Artifact\BuildArtifact $buildArtifact = null; + private ?Finder\SplFileInfo $artifactFile = null; /** * @var array @@ -66,14 +67,17 @@ public function setMirrored(bool $mirrored): self return $this; } - public function getBuildArtifact(): ?Artifact\BuildArtifact + public function getArtifactFile(): ?Finder\SplFileInfo { - return $this->buildArtifact; + return $this->artifactFile; } - public function setBuildArtifact(Artifact\BuildArtifact $buildArtifact): self + /** + * @impure + */ + public function setArtifactFile(?Finder\SplFileInfo $artifactFile): self { - $this->buildArtifact = $buildArtifact; + $this->artifactFile = $artifactFile; return $this; } diff --git a/src/Builder/Config/ConfigFactory.php b/src/Builder/Config/ConfigFactory.php index 8ce77ba7..2e85e3ab 100644 --- a/src/Builder/Config/ConfigFactory.php +++ b/src/Builder/Config/ConfigFactory.php @@ -25,6 +25,7 @@ use CPSIT\ProjectBuilder\Exception; use CPSIT\ProjectBuilder\Helper; +use CPSIT\ProjectBuilder\Json; use CPSIT\ProjectBuilder\Paths; use CuyZ\Valinor\Cache; use CuyZ\Valinor\Mapper; @@ -50,7 +51,7 @@ final class ConfigFactory private function __construct( private readonly Mapper\TreeMapper $mapper, - private readonly JsonSchema\Validator $validator, + private readonly Json\SchemaValidator $schemaValidator, ) { } @@ -65,7 +66,7 @@ public static function create(): self ->mapper() ; - return new self($mapper, new JsonSchema\Validator()); + return new self($mapper, new Json\SchemaValidator(new JsonSchema\Validator())); } public function buildFromFile(string $file, string $identifier): Config @@ -87,7 +88,7 @@ public function buildFromFile(string $file, string $identifier): Config public function buildFromString(string $content, string $identifier, FileType $fileType): Config { $parsedContent = $this->parseContent($content, $fileType); - $validationResult = $this->validateConfig($parsedContent); + $validationResult = $this->schemaValidator->validate($parsedContent, Paths::PROJECT_SCHEMA_CONFIG); if (!$validationResult->isValid()) { throw Exception\InvalidConfigurationException::forValidationErrors($validationResult->error()); @@ -98,24 +99,6 @@ public function buildFromString(string $content, string $identifier, FileType $f return $this->mapper->map(Config::class, $source); } - private function validateConfig(stdClass $parsedContent): JsonSchema\ValidationResult - { - $schemaFile = Filesystem\Path::join(Helper\FilesystemHelper::getPackageDirectory(), Paths::PROJECT_SCHEMA_CONFIG); - $schemaReference = 'file://'.$schemaFile; - $schemaResolver = $this->validator->resolver(); - - // @codeCoverageIgnoreStart - if (null === $schemaResolver) { - $schemaResolver = new JsonSchema\Resolvers\SchemaResolver(); - $this->validator->setResolver($schemaResolver); - } - // @codeCoverageIgnoreEnd - - $schemaResolver->registerFile($schemaReference, $schemaFile); - - return $this->validator->validate($parsedContent, $schemaReference); - } - private function generateMapperSource(string $content, string $identifier, FileType $fileType): Mapper\Source\Source { $parsedContent = match ($fileType) { diff --git a/src/Builder/Generator/Generator.php b/src/Builder/Generator/Generator.php index 93e1f96f..59e5b01c 100644 --- a/src/Builder/Generator/Generator.php +++ b/src/Builder/Generator/Generator.php @@ -54,6 +54,7 @@ public function __construct( private readonly Filesystem\Filesystem $filesystem, private readonly EventDispatcher\EventDispatcherInterface $eventDispatcher, private readonly Builder\Writer\JsonFileWriter $writer, + private readonly Builder\ArtifactGenerator $artifactGenerator, ) { } @@ -113,7 +114,7 @@ public function run(string $targetDirectory): Builder\BuildResult public function dumpArtifact(Builder\BuildResult $result): void { - $step = new Builder\Generator\Step\DumpBuildArtifactStep($this->filesystem, $this->writer); + $step = new Builder\Generator\Step\DumpBuildArtifactStep($this->filesystem, $this->writer, $this->artifactGenerator); $step->run($result); } diff --git a/src/Builder/Generator/Step/CleanUpStep.php b/src/Builder/Generator/Step/CleanUpStep.php index 6eda3d94..75418fa6 100644 --- a/src/Builder/Generator/Step/CleanUpStep.php +++ b/src/Builder/Generator/Step/CleanUpStep.php @@ -87,19 +87,19 @@ public static function supports(string $type): bool private function backupBuildArtifact(Builder\BuildResult $buildResult): ?Finder\SplFileInfo { - $buildArtifact = $buildResult->getBuildArtifact(); + $artifactFile = $buildResult->getArtifactFile(); - if (null === $buildArtifact || !$this->filesystem->exists($buildArtifact->getFile()->getPathname())) { + if (null === $artifactFile || !$this->filesystem->exists($artifactFile->getPathname())) { return null; } $backupFile = Helper\FilesystemHelper::createFileObject( Helper\FilesystemHelper::getNewTemporaryDirectory(), - $buildArtifact->getFile()->getFilename(), + $artifactFile->getFilename(), ); $this->filesystem->copy( - $buildArtifact->getFile()->getPathname(), + $artifactFile->getPathname(), $backupFile->getPathname(), ); @@ -108,13 +108,13 @@ private function backupBuildArtifact(Builder\BuildResult $buildResult): ?Finder\ private function restoreBuildArtifact(Builder\BuildResult $buildResult, Finder\SplFileInfo $artifactBackup): void { - if (null === $buildResult->getBuildArtifact()) { + if (null === $buildResult->getArtifactFile()) { return; // @codeCoverageIgnore } $this->filesystem->copy( $artifactBackup->getPathname(), - $buildResult->getBuildArtifact()->getFile()->getPathname(), + $buildResult->getArtifactFile()->getPathname(), ); $this->filesystem->remove($artifactBackup->getPath()); } diff --git a/src/Builder/Generator/Step/DumpBuildArtifactStep.php b/src/Builder/Generator/Step/DumpBuildArtifactStep.php index da77548c..83b01d0b 100644 --- a/src/Builder/Generator/Step/DumpBuildArtifactStep.php +++ b/src/Builder/Generator/Step/DumpBuildArtifactStep.php @@ -24,6 +24,8 @@ namespace CPSIT\ProjectBuilder\Builder\Generator\Step; use CPSIT\ProjectBuilder\Builder; +use CPSIT\ProjectBuilder\Helper; +use CPSIT\ProjectBuilder\Resource; use Symfony\Component\Filesystem; /** @@ -41,27 +43,31 @@ final class DumpBuildArtifactStep extends AbstractStep public function __construct( private readonly Filesystem\Filesystem $filesystem, private readonly Builder\Writer\JsonFileWriter $writer, + private readonly Builder\ArtifactGenerator $artifactGenerator, ) { parent::__construct(); } public function run(Builder\BuildResult $buildResult): bool { - $buildArtifact = $buildResult->getBuildArtifact(); + $artifactFile = $buildResult->getArtifactFile(); - if (null === $buildArtifact) { + if (null === $artifactFile) { return true; } $buildResult->applyStep($this); - return $this->writer->write($buildArtifact->getFile(), $buildArtifact); + $composer = Resource\Local\Composer::createComposer(Helper\FilesystemHelper::getWorkingDirectory()); + $artifact = $this->artifactGenerator->build($artifactFile, $buildResult, $composer->getPackage()); + + return $this->writer->write($artifactFile, $artifact); } public function revert(Builder\BuildResult $buildResult): void { - if (null !== $buildResult->getBuildArtifact()) { - $this->filesystem->remove($buildResult->getBuildArtifact()->getFile()->getPathname()); + if (null !== $buildResult->getArtifactFile()) { + $this->filesystem->remove($buildResult->getArtifactFile()->getPathname()); } } diff --git a/src/Builder/Generator/Step/GenerateBuildArtifactStep.php b/src/Builder/Generator/Step/GenerateBuildArtifactStep.php index 34385bd1..b0a62d9c 100644 --- a/src/Builder/Generator/Step/GenerateBuildArtifactStep.php +++ b/src/Builder/Generator/Step/GenerateBuildArtifactStep.php @@ -26,7 +26,6 @@ use CPSIT\ProjectBuilder\Builder; use CPSIT\ProjectBuilder\Helper; use CPSIT\ProjectBuilder\IO; -use CPSIT\ProjectBuilder\Resource; use Symfony\Component\Filesystem; use Symfony\Component\Finder; @@ -64,13 +63,7 @@ public function run(Builder\BuildResult $buildResult): bool return !$this->stopped; } - $artifact = new Builder\Artifact\BuildArtifact( - $artifactFile->getRelativePathname(), - $buildResult, - Resource\Local\Composer::createComposer(Helper\FilesystemHelper::getPackageDirectory())->getPackage(), - ); - - $buildResult->setBuildArtifact($artifact); + $buildResult->setArtifactFile($artifactFile); $buildResult->applyStep($this); return true; diff --git a/src/Console/Command/CreateProjectCommand.php b/src/Console/Command/CreateProjectCommand.php index 9182eab0..13329e04 100644 --- a/src/Console/Command/CreateProjectCommand.php +++ b/src/Console/Command/CreateProjectCommand.php @@ -50,36 +50,27 @@ final class CreateProjectCommand extends Command\BaseCommand private const ABORTED = 2; /** - * @var non-empty-list - */ - private array $templateProviders; - - /** - * @param list $templateProviders + * @param non-empty-list $templateProviders */ public function __construct( private readonly IO\Messenger $messenger, private readonly Builder\Config\ConfigReader $configReader, private readonly Error\ErrorHandler $errorHandler, - private readonly Filesystem\Filesystem $filesystem, - array $templateProviders = [], + private array $templateProviders, ) { parent::__construct('project:create'); - - if ([] === $templateProviders) { - $templateProviders = $this->createDefaultTemplateProviders(); - } - - $this->templateProviders = $templateProviders; } public static function create(IO\Messenger $messenger): self { + $filesystem = new Filesystem\Filesystem(); + $providerFactory = new Template\Provider\ProviderFactory($messenger, $filesystem); + return new self( $messenger, Builder\Config\ConfigReader::create(), new Error\ErrorHandler($messenger), - new Filesystem\Filesystem(), + $providerFactory->getAll(), ); } @@ -203,16 +194,4 @@ private function buildContainer(Builder\Config\Config $config): \Symfony\Compone return $container; } - - /** - * @return non-empty-list - */ - private function createDefaultTemplateProviders(): array - { - return [ - new Template\Provider\PackagistProvider($this->messenger, $this->filesystem), - new Template\Provider\ComposerProvider($this->messenger, $this->filesystem), - new Template\Provider\VcsProvider($this->messenger, $this->filesystem), - ]; - } } diff --git a/src/Exception/InvalidArtifactException.php b/src/Exception/InvalidArtifactException.php new file mode 100644 index 00000000..a17c79e8 --- /dev/null +++ b/src/Exception/InvalidArtifactException.php @@ -0,0 +1,77 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Exception; + +use Opis\JsonSchema; + +use function sprintf; + +/** + * InvalidArtifactException. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class InvalidArtifactException extends Exception +{ + public static function forFile(string $file): self + { + return new self( + sprintf('The artifact file "%s" is invalid and cannot be processed.', $file), + 1677141460, + ); + } + + public static function forPath(string $path): self + { + return new self( + sprintf('Invalid value at "%s" in artifact.', $path), + 1677140440, + ); + } + + public static function forInvalidVersion(): self + { + return new self('Unable to detect a valid artifact version.', 1677141758); + } + + public static function forValidationErrors(?JsonSchema\Errors\ValidationError $error): self + { + $decoratedErrors = ''; + + if (null !== $error) { + $formatter = new JsonSchema\Errors\ErrorFormatter(); + $formattedErrors = $formatter->format($error, false); + + foreach ($formattedErrors as $path => $errorMessage) { + $decoratedErrors .= PHP_EOL.sprintf(' * Error at property path "%s": %s', $path, $errorMessage); + } + } + + return new self( + sprintf('The artifact does not match the build artifact schema.%s', $decoratedErrors), + 1677601857, + ); + } +} diff --git a/src/Exception/UnknownTemplateProviderException.php b/src/Exception/UnknownTemplateProviderException.php new file mode 100644 index 00000000..810abd45 --- /dev/null +++ b/src/Exception/UnknownTemplateProviderException.php @@ -0,0 +1,43 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Exception; + +use function sprintf; + +/** + * UnknownTemplateProviderException. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class UnknownTemplateProviderException extends Exception +{ + public static function create(string $identifier): self + { + return new self( + sprintf('The template provider "%s" does not exist or is not available.', $identifier), + 1677140774, + ); + } +} diff --git a/src/Helper/ArrayHelper.php b/src/Helper/ArrayHelper.php index a370c27a..2fa25370 100644 --- a/src/Helper/ArrayHelper.php +++ b/src/Helper/ArrayHelper.php @@ -25,6 +25,9 @@ use ArrayObject; +use function count; +use function is_array; + /** * ArrayHelper. * @@ -78,6 +81,33 @@ public static function setValueByPath(array|ArrayObject &$subject, string $path, $reference = $value; } + /** + * @param iterable $subject + * @param non-empty-string $path + */ + public static function removeByPath(iterable &$subject, string $path): void + { + $pathSegments = array_filter(explode('.', $path)); + $maxSegmentIndex = count($pathSegments) - 1; + $reference = &$subject; + + foreach ($pathSegments as $currentSegmentIndex => $pathSegment) { + // Early return if given path segment does not exist + if (!self::pathSegmentExists($reference, $pathSegment)) { + return; + } + + // Unset target path + if ($currentSegmentIndex === $maxSegmentIndex) { + unset($reference[$pathSegment]); + + return; + } + + $reference = &$reference[$pathSegment]; + } + } + private static function pathSegmentExists(mixed $subject, string $pathSegment): bool { if (!is_array($subject) && !($subject instanceof ArrayObject)) { diff --git a/src/Json/SchemaValidator.php b/src/Json/SchemaValidator.php new file mode 100644 index 00000000..2bbbd8fb --- /dev/null +++ b/src/Json/SchemaValidator.php @@ -0,0 +1,59 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Json; + +use CPSIT\ProjectBuilder\Helper; +use Opis\JsonSchema; + +/** + * SchemaValidator. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class SchemaValidator +{ + public function __construct( + private readonly JsonSchema\Validator $validator, + ) { + } + + public function validate(mixed $data, string $schemaFile): JsonSchema\ValidationResult + { + $schemaFile = Helper\FilesystemHelper::resolveRelativePath($schemaFile); + $schemaReference = 'file://'.$schemaFile; + $schemaResolver = $this->validator->resolver(); + + // @codeCoverageIgnoreStart + if (null === $schemaResolver) { + $schemaResolver = new JsonSchema\Resolvers\SchemaResolver(); + $this->validator->setResolver($schemaResolver); + } + // @codeCoverageIgnoreEnd + + $schemaResolver->registerFile($schemaReference, $schemaFile); + + return $this->validator->validate($data, $schemaReference); + } +} diff --git a/src/Template/Provider/ComposerProvider.php b/src/Template/Provider/ComposerProvider.php index 3f256409..7dbc140e 100644 --- a/src/Template/Provider/ComposerProvider.php +++ b/src/Template/Provider/ComposerProvider.php @@ -109,9 +109,4 @@ public static function getType(): string { return self::TYPE; } - - public static function supports(string $type): bool - { - return self::TYPE === $type; - } } diff --git a/src/Template/Provider/PackagistProvider.php b/src/Template/Provider/PackagistProvider.php index 28eefe01..0c267285 100644 --- a/src/Template/Provider/PackagistProvider.php +++ b/src/Template/Provider/PackagistProvider.php @@ -52,9 +52,4 @@ public static function getType(): string { return self::TYPE; } - - public static function supports(string $type): bool - { - return self::TYPE === $type; - } } diff --git a/src/Template/Provider/ProviderFactory.php b/src/Template/Provider/ProviderFactory.php index c53c4c68..d4a86629 100644 --- a/src/Template/Provider/ProviderFactory.php +++ b/src/Template/Provider/ProviderFactory.php @@ -24,6 +24,8 @@ namespace CPSIT\ProjectBuilder\Template\Provider; use CPSIT\ProjectBuilder\Exception; +use CPSIT\ProjectBuilder\IO; +use Symfony\Component\Filesystem; /** * ProviderFactory. @@ -34,21 +36,41 @@ final class ProviderFactory { /** - * @param iterable $providers + * @var non-empty-list */ + private readonly array $providers; + public function __construct( - private readonly iterable $providers, + IO\Messenger $messenger, + Filesystem\Filesystem $filesystem, ) { + $this->providers = [ + // sorted by priority + new PackagistProvider($messenger, $filesystem), + new ComposerProvider($messenger, $filesystem), + new VcsProvider($messenger, $filesystem), + ]; } + /** + * @throws Exception\UnknownTemplateProviderException + */ public function get(string $type): ProviderInterface { foreach ($this->providers as $provider) { - if ($provider::supports($type)) { + if ($type === $provider::getType()) { return $provider; } } - throw Exception\UnsupportedTypeException::create($type); + throw Exception\UnknownTemplateProviderException::create($type); + } + + /** + * @return non-empty-list + */ + public function getAll(): array + { + return $this->providers; } } diff --git a/src/Template/Provider/ProviderInterface.php b/src/Template/Provider/ProviderInterface.php index 5dd019e4..dfb90db3 100644 --- a/src/Template/Provider/ProviderInterface.php +++ b/src/Template/Provider/ProviderInterface.php @@ -47,6 +47,4 @@ public function installTemplateSource(Template\TemplateSource $templateSource): public static function getName(): string; public static function getType(): string; - - public static function supports(string $type): bool; } diff --git a/src/Template/Provider/VcsProvider.php b/src/Template/Provider/VcsProvider.php index 95e1ffb2..dc938681 100644 --- a/src/Template/Provider/VcsProvider.php +++ b/src/Template/Provider/VcsProvider.php @@ -161,9 +161,4 @@ public static function getType(): string { return self::TYPE; } - - public static function supports(string $type): bool - { - return self::TYPE === $type; - } } diff --git a/tests/config/services.yaml b/tests/config/services.yaml index 07f3abce..c8220309 100644 --- a/tests/config/services.yaml +++ b/tests/config/services.yaml @@ -16,3 +16,6 @@ services: CPSIT\ProjectBuilder\Tests\Fixtures\DummyTemplateRenderingEventListener: tags: ['event.listener'] + + CPSIT\ProjectBuilder\Tests\Fixtures\DummyMigration: + tags: ['artifact.migration'] diff --git a/tests/src/Builder/Artifact/ArtifactTest.php b/tests/src/Builder/Artifact/ArtifactTest.php new file mode 100644 index 00000000..b2bde477 --- /dev/null +++ b/tests/src/Builder/Artifact/ArtifactTest.php @@ -0,0 +1,109 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +/** + * ArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\BuildArtifact $buildArtifact; + private Src\Builder\Artifact\TemplateArtifact $templateArtifact; + private Src\Builder\Artifact\GeneratorArtifact $generatorArtifact; + private Src\Builder\Artifact\ResultArtifact $resultArtifact; + private Src\Builder\Artifact\Artifact $subject; + + protected function setUp(): void + { + $this->buildArtifact = new Src\Builder\Artifact\BuildArtifact(1, 'file', 123); + $this->templateArtifact = new Src\Builder\Artifact\TemplateArtifact( + 'identifier', + 'hash', + new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ), + [ + 'name' => 'name', + 'url' => 'url', + ], + ); + $this->generatorArtifact = new Src\Builder\Artifact\GeneratorArtifact( + new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ), + 'composer', + ); + $this->resultArtifact = new Src\Builder\Artifact\ResultArtifact( + [ + 'foo' => 'foo', + 'baz' => 'baz', + ], + [ + [ + 'type' => 'type', + 'applied' => true, + ], + ], + [ + [ + 'source' => 'source', + 'target' => 'target', + ], + ], + ); + $this->subject = new Src\Builder\Artifact\Artifact( + $this->buildArtifact, + $this->templateArtifact, + $this->generatorArtifact, + $this->resultArtifact, + ); + } + + #[Framework\Attributes\Test] + public function dumpReturnsDumpedArtifact(): void + { + $expected = [ + 'artifact' => $this->buildArtifact, + 'template' => $this->templateArtifact, + 'generator' => $this->generatorArtifact, + 'result' => $this->resultArtifact, + ]; + + self::assertSame($expected, $this->subject->dump()); + } +} diff --git a/tests/src/Builder/Artifact/BuildArtifactTest.php b/tests/src/Builder/Artifact/BuildArtifactTest.php new file mode 100644 index 00000000..3dd5d501 --- /dev/null +++ b/tests/src/Builder/Artifact/BuildArtifactTest.php @@ -0,0 +1,60 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +use function json_encode; + +/** + * BuildArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class BuildArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\BuildArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\BuildArtifact(1, 'file', 123); + } + + #[Framework\Attributes\Test] + public function artifactIsJsonSerializable(): void + { + $expected = [ + 'version' => $this->subject->version, + 'path' => $this->subject->path, + 'date' => $this->subject->date, + ]; + + self::assertJsonStringEqualsJsonString( + json_encode($expected, JSON_THROW_ON_ERROR), + json_encode($this->subject, JSON_THROW_ON_ERROR), + ); + } +} diff --git a/tests/src/Builder/Artifact/GeneratorArtifactTest.php b/tests/src/Builder/Artifact/GeneratorArtifactTest.php new file mode 100644 index 00000000..c028f961 --- /dev/null +++ b/tests/src/Builder/Artifact/GeneratorArtifactTest.php @@ -0,0 +1,68 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +use function json_encode; + +/** + * GeneratorArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class GeneratorArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\GeneratorArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\GeneratorArtifact( + new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ), + 'composer', + ); + } + + #[Framework\Attributes\Test] + public function artifactIsJsonSerializable(): void + { + $expected = [ + 'package' => $this->subject->package, + 'executor' => $this->subject->executor, + ]; + + self::assertJsonStringEqualsJsonString( + json_encode($expected, JSON_THROW_ON_ERROR), + json_encode($this->subject, JSON_THROW_ON_ERROR), + ); + } +} diff --git a/tests/src/Builder/Artifact/Migration/BaseMigrationTest.php b/tests/src/Builder/Artifact/Migration/BaseMigrationTest.php new file mode 100644 index 00000000..a661cd13 --- /dev/null +++ b/tests/src/Builder/Artifact/Migration/BaseMigrationTest.php @@ -0,0 +1,128 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact\Migration; + +use CPSIT\ProjectBuilder\Tests; +use PHPUnit\Framework; +use PHPUnit\Framework\Attributes\Test; + +use function strrev; + +/** + * BaseMigrationTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class BaseMigrationTest extends Framework\TestCase +{ + private Tests\Fixtures\DummyMigration $subject; + + /** + * @var array + */ + private array $artifact; + + protected function setUp(): void + { + $this->subject = new Tests\Fixtures\DummyMigration(); + $this->artifact = [ + 'foo' => [ + 'baz' => 'hello world', + ], + 'baz' => 'dummy', + ]; + } + + #[Framework\Attributes\Test] + public function remapValueCanRemapPathToOtherPath(): void + { + $this->subject->remapArguments = [ + 'foo.baz', + 'baz', + ]; + + $expected = [ + 'foo' => [], + 'baz' => 'hello world', + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } + + #[Framework\Attributes\Test] + public function remapValueCanRemapPathToOtherValue(): void + { + $this->subject->remapArguments = [ + 'foo.baz', + null, + 'bye!', + ]; + + $expected = [ + 'foo' => [ + 'baz' => 'bye!', + ], + 'baz' => 'dummy', + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } + + #[Framework\Attributes\Test] + public function remapValueCanRemapPathToOtherValueFromCallable(): void + { + $this->subject->remapArguments = [ + 'foo.baz', + null, + static fn (string $currentValue) => strrev($currentValue), + ]; + + $expected = [ + 'foo' => [ + 'baz' => 'dlrow olleh', + ], + 'baz' => 'dummy', + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } + + #[Framework\Attributes\Test] + public function remapValueCanRemapPathToOtherPathAndValue(): void + { + $this->subject->remapArguments = [ + 'foo.baz', + 'baz', + static fn (string $currentValue) => strrev($currentValue), + ]; + + $expected = [ + 'foo' => [], + 'baz' => 'dlrow olleh', + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } +} diff --git a/tests/src/Builder/Artifact/Migration/Migration1679497137Test.php b/tests/src/Builder/Artifact/Migration/Migration1679497137Test.php new file mode 100644 index 00000000..6906400e --- /dev/null +++ b/tests/src/Builder/Artifact/Migration/Migration1679497137Test.php @@ -0,0 +1,65 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact\Migration; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +/** + * Migration1679497137Test. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class Migration1679497137Test extends Framework\TestCase +{ + private Src\Builder\Artifact\Migration\Migration1679497137 $subject; + + /** + * @var array + */ + private array $artifact; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\Migration\Migration1679497137(); + $this->artifact = [ + 'artifact' => [ + 'file' => 'foo', + ], + ]; + } + + #[Framework\Attributes\Test] + public function migrateMigratesArtifactFileToArtifactPath(): void + { + $expected = [ + 'artifact' => [ + 'path' => 'foo', + ], + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } +} diff --git a/tests/src/Builder/Artifact/PackageArtifactTest.php b/tests/src/Builder/Artifact/PackageArtifactTest.php new file mode 100644 index 00000000..b709812c --- /dev/null +++ b/tests/src/Builder/Artifact/PackageArtifactTest.php @@ -0,0 +1,68 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +use function json_encode; + +/** + * PackageArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class PackageArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\PackageArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ); + } + + #[Framework\Attributes\Test] + public function artifactIsJsonSerializable(): void + { + $expected = [ + 'name' => $this->subject->name, + 'version' => $this->subject->version, + 'sourceReference' => $this->subject->sourceReference, + 'sourceUrl' => $this->subject->sourceUrl, + 'distUrl' => $this->subject->distUrl, + ]; + + self::assertJsonStringEqualsJsonString( + json_encode($expected, JSON_THROW_ON_ERROR), + json_encode($this->subject, JSON_THROW_ON_ERROR), + ); + } +} diff --git a/tests/src/Builder/Artifact/ResultArtifactTest.php b/tests/src/Builder/Artifact/ResultArtifactTest.php new file mode 100644 index 00000000..8eba90f7 --- /dev/null +++ b/tests/src/Builder/Artifact/ResultArtifactTest.php @@ -0,0 +1,77 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +use function json_encode; + +/** + * ResultArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ResultArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\ResultArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\ResultArtifact( + [ + 'foo' => 'foo', + 'baz' => 'baz', + ], + [ + [ + 'type' => 'type', + 'applied' => true, + ], + ], + [ + [ + 'source' => 'source', + 'target' => 'target', + ], + ], + ); + } + + #[Framework\Attributes\Test] + public function artifactIsJsonSerializable(): void + { + $expected = [ + 'properties' => $this->subject->properties, + 'steps' => $this->subject->steps, + 'processedFiles' => $this->subject->processedFiles, + ]; + + self::assertJsonStringEqualsJsonString( + json_encode($expected, JSON_THROW_ON_ERROR), + json_encode($this->subject, JSON_THROW_ON_ERROR), + ); + } +} diff --git a/tests/src/Builder/Artifact/TemplateArtifactTest.php b/tests/src/Builder/Artifact/TemplateArtifactTest.php new file mode 100644 index 00000000..8248bebd --- /dev/null +++ b/tests/src/Builder/Artifact/TemplateArtifactTest.php @@ -0,0 +1,75 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder\Artifact; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +use function json_encode; + +/** + * TemplateArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class TemplateArtifactTest extends Framework\TestCase +{ + private Src\Builder\Artifact\TemplateArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\TemplateArtifact( + 'identifier', + 'hash', + new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ), + [ + 'name' => 'name', + 'url' => 'url', + ], + ); + } + + #[Framework\Attributes\Test] + public function artifactIsJsonSerializable(): void + { + $expected = [ + 'identifier' => $this->subject->identifier, + 'hash' => $this->subject->hash, + 'package' => $this->subject->package, + 'provider' => $this->subject->provider, + ]; + + self::assertJsonStringEqualsJsonString( + json_encode($expected, JSON_THROW_ON_ERROR), + json_encode($this->subject, JSON_THROW_ON_ERROR), + ); + } +} diff --git a/tests/src/Builder/ArtifactGeneratorTest.php b/tests/src/Builder/ArtifactGeneratorTest.php new file mode 100644 index 00000000..75ef043b --- /dev/null +++ b/tests/src/Builder/ArtifactGeneratorTest.php @@ -0,0 +1,132 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder; + +use Composer\Package; +use CPSIT\ProjectBuilder as Src; +use CPSIT\ProjectBuilder\Tests; +use PHPUnit\Framework; +use Symfony\Component\Finder; + +use function dirname; + +/** + * ArtifactGeneratorTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ArtifactGeneratorTest extends Tests\ContainerAwareTestCase +{ + private Src\Builder\ArtifactGenerator $subject; + private Finder\SplFileInfo $artifactFile; + private Src\Builder\BuildResult $buildResult; + private Package\RootPackageInterface $rootPackage; + + protected function setUp(): void + { + $this->subject = self::$container->get(Src\Builder\ArtifactGenerator::class); + $this->artifactFile = Src\Helper\FilesystemHelper::createFileObject( + Src\Helper\FilesystemHelper::getNewTemporaryDirectory(), + 'build-artifact.json', + ); + $this->buildResult = new Src\Builder\BuildResult( + new Src\Builder\BuildInstructions( + self::$container->get('app.config'), + 'foo', + ), + ); + $this->rootPackage = Src\Resource\Local\Composer::createComposer(dirname(__DIR__, 3))->getPackage(); + } + + #[Framework\Attributes\Test] + public function buildGeneratesArtifact(): void + { + $writtenDirectory = $this->buildResult->getWrittenDirectory(); + $step = new Tests\Fixtures\DummyStep(); + $step->addProcessedFile( + new Src\Resource\Local\ProcessedFile( + Src\Helper\FilesystemHelper::createFileObject($writtenDirectory, 'foo.json'), + Src\Helper\FilesystemHelper::createFileObject($writtenDirectory, 'baz.json'), + ), + ); + + $this->buildResult->getInstructions()->addTemplateVariable('foo', 'baz'); + $this->buildResult->applyStep($step); + + $actual = $this->subject->build($this->artifactFile, $this->buildResult, $this->rootPackage); + + $expected = new Src\Builder\Artifact\Artifact( + new Src\Builder\Artifact\BuildArtifact( + Src\Builder\ArtifactGenerator::VERSION, + 'build-artifact.json', + $actual->artifact->date, + ), + new Src\Builder\Artifact\TemplateArtifact( + 'test', + $actual->template->hash, + new Src\Builder\Artifact\PackageArtifact( + 'foo/baz', + '1.0.0', + null, + null, + null, + ), + [ + 'name' => 'dummy', + 'url' => 'https://www.example.com', + ], + ), + new Src\Builder\Artifact\GeneratorArtifact( + new Src\Builder\Artifact\PackageArtifact( + $this->rootPackage->getName(), + $this->rootPackage->getPrettyVersion(), + $this->rootPackage->getSourceReference(), + $this->rootPackage->getSourceUrl(), + $this->rootPackage->getDistUrl(), + ), + 'composer', + ), + new Src\Builder\Artifact\ResultArtifact( + [ + 'foo' => 'baz', + ], + [ + [ + 'type' => 'dummy', + 'applied' => true, + ], + ], + [ + [ + 'source' => 'foo.json', + 'target' => 'baz.json', + ], + ], + ), + ); + + self::assertEquals($expected, $actual); + } +} diff --git a/tests/src/Builder/ArtifactReaderTest.php b/tests/src/Builder/ArtifactReaderTest.php new file mode 100644 index 00000000..3431f366 --- /dev/null +++ b/tests/src/Builder/ArtifactReaderTest.php @@ -0,0 +1,117 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Builder; + +use CPSIT\ProjectBuilder as Src; +use CPSIT\ProjectBuilder\Tests; +use PHPUnit\Framework; + +use function dirname; + +/** + * ArtifactReaderTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ArtifactReaderTest extends Tests\ContainerAwareTestCase +{ + private string $artifactFile; + private Tests\Fixtures\DummyMigration $migration; + private Src\Builder\ArtifactReader $subject; + + protected function setUp(): void + { + $this->artifactFile = dirname(__DIR__).'/Fixtures/Files/build-artifact.json'; + $this->migration = self::$container->get(Tests\Fixtures\DummyMigration::class); + $this->subject = self::$container->get(Src\Builder\ArtifactReader::class); + } + + #[Framework\Attributes\Test] + public function fromFileThrowsExceptionIfFileIsNotReadable(): void + { + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile('foo')); + + $this->subject->fromFile('foo'); + } + + #[Framework\Attributes\Test] + public function fromFileThrowsExceptionIfJsonIsInvalid(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-json.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile($file)); + + $this->subject->fromFile($file); + } + + #[Framework\Attributes\Test] + public function fromFileThrowsExceptionIfJsonIsUnsupported(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-artifact.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile($file)); + + $this->subject->fromFile($file); + } + + #[Framework\Attributes\Test] + public function fromFileThrowsExceptionIfArtifactVersionIsInvalid(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-artifact-version.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forInvalidVersion()); + + $this->subject->fromFile($file); + } + + #[Framework\Attributes\Test] + public function fromFileThrowsExceptionIfMigratedArtifactIsInvalid(): void + { + $this->migration->remapArguments = [ + 'artifact', + null, + 'foo', + ]; + + $this->expectException(Src\Exception\InvalidArtifactException::class); + $this->expectExceptionCode(1677601857); + + $this->subject->fromFile($this->artifactFile); + } + + #[Framework\Attributes\Test] + public function fromFilePerformsMigrations(): void + { + $this->migration->remapArguments = [ + 'generator.executor', + null, + 'docker', + ]; + + $actual = $this->subject->fromFile($this->artifactFile); + + self::assertSame('docker', $actual->generator->executor); + } +} diff --git a/tests/src/Builder/BuildResultTest.php b/tests/src/Builder/BuildResultTest.php index 7ea81f93..5296840c 100644 --- a/tests/src/Builder/BuildResultTest.php +++ b/tests/src/Builder/BuildResultTest.php @@ -23,7 +23,6 @@ namespace CPSIT\ProjectBuilder\Tests\Builder; -use Composer\Package; use CPSIT\ProjectBuilder as Src; use CPSIT\ProjectBuilder\Tests; use PHPUnit\Framework; @@ -49,7 +48,9 @@ protected function setUp(): void self::$container->get('app.config'), 'foo', ); - $this->subject = new Src\Builder\BuildResult($this->instructions); + $this->subject = new Src\Builder\BuildResult( + $this->instructions, + ); } #[Framework\Attributes\Test] @@ -66,17 +67,13 @@ public function isMirroredReturnsMirrorState(): void } #[Framework\Attributes\Test] - public function getBuildArtifactReturnsBuildArtifact(): void + public function getArtifactFileReturnsArtifactFile(): void { - self::assertNull($this->subject->getBuildArtifact()); + self::assertNull($this->subject->getArtifactFile()); - $buildArtifact = new Src\Builder\Artifact\BuildArtifact( - 'foo.json', - $this->subject, - new Package\RootPackage('foo/baz', '1.0.0', '1.0.0'), - ); + $artifactFile = Src\Helper\FilesystemHelper::createFileObject('/foo', 'baz'); - self::assertSame($buildArtifact, $this->subject->setBuildArtifact($buildArtifact)->getBuildArtifact()); + self::assertSame($artifactFile, $this->subject->setArtifactFile($artifactFile)->getArtifactFile()); } #[Framework\Attributes\Test] diff --git a/tests/src/Builder/Generator/GeneratorTest.php b/tests/src/Builder/Generator/GeneratorTest.php index c3462f41..cd62b8a6 100644 --- a/tests/src/Builder/Generator/GeneratorTest.php +++ b/tests/src/Builder/Generator/GeneratorTest.php @@ -205,6 +205,7 @@ public function runRevertsAppliedStepsAndExistsIfStoppableStepFailed(): void $this->filesystem, self::$container->get(EventDispatcher\EventDispatcherInterface::class), self::$container->get(Src\Builder\Writer\JsonFileWriter::class), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $actual = $subject->run($this->targetDirectory); diff --git a/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php b/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php index 66e9113c..b11bdc8f 100644 --- a/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php +++ b/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php @@ -28,7 +28,9 @@ use CPSIT\ProjectBuilder\Tests; use PHPUnit\Framework; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; +use function dirname; use function json_encode; /** @@ -41,52 +43,59 @@ final class DumpBuildArtifactStepTest extends Tests\ContainerAwareTestCase { private Filesystem\Filesystem $filesystem; private Src\Builder\Generator\Step\DumpBuildArtifactStep $subject; + private Src\Builder\ArtifactGenerator $artifactGenerator; private Src\Builder\BuildResult $buildResult; - private Src\Builder\Artifact\BuildArtifact $buildArtifact; + private Finder\SplFileInfo $artifactFile; + private Package\RootPackageInterface $rootPackage; protected function setUp(): void { $this->filesystem = self::$container->get(Filesystem\Filesystem::class); + $this->artifactGenerator = self::$container->get(Src\Builder\ArtifactGenerator::class); $this->subject = new Src\Builder\Generator\Step\DumpBuildArtifactStep( $this->filesystem, self::$container->get(Src\Builder\Writer\JsonFileWriter::class), + $this->artifactGenerator, ); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), ); - $this->buildArtifact = new Src\Builder\Artifact\BuildArtifact( - 'foo.json', - $this->buildResult, - new Package\RootPackage('foo/baz', '1.0.0', '1.0.0'), + $this->artifactFile = Src\Helper\FilesystemHelper::createFileObject( + $this->buildResult->getWrittenDirectory(), + '.build/build-artifact.json', ); + $this->rootPackage = Src\Resource\Local\Composer::createComposer(dirname(__DIR__, 5))->getPackage(); } #[Framework\Attributes\Test] - public function runDoesNothingIfBuildArtifactWasNotGenerated(): void + public function runDoesNothingIfArtifactWasNotGenerated(): void { self::assertTrue($this->subject->run($this->buildResult)); self::assertFalse($this->buildResult->isStepApplied($this->subject)); - self::assertFileDoesNotExist($this->buildArtifact->getFile()->getPathname()); + self::assertFileDoesNotExist($this->artifactFile->getPathname()); } #[Framework\Attributes\Test] - public function runDumpsBuildArtifact(): void + public function runDumpsArtifact(): void { - $this->buildResult->setBuildArtifact($this->buildArtifact); + $this->buildResult->setArtifactFile($this->artifactFile); self::assertTrue($this->subject->run($this->buildResult)); self::assertTrue($this->buildResult->isStepApplied($this->subject)); - self::assertFileExists($this->buildArtifact->getFile()->getPathname()); + self::assertFileExists($this->artifactFile->getPathname()); + + $artifact = $this->artifactGenerator->build($this->artifactFile, $this->buildResult, $this->rootPackage); + self::assertJsonStringEqualsJsonFile( - $this->buildArtifact->getFile()->getPathname(), - json_encode($this->buildArtifact, JSON_THROW_ON_ERROR), + $this->artifactFile->getPathname(), + json_encode($artifact, JSON_THROW_ON_ERROR), ); } #[Framework\Attributes\Test] - public function revertDoesNothingIfBuildArtifactWasNotGenerated(): void + public function revertDoesNothingIfArtifactWasNotGenerated(): void { - $artifactPath = $this->buildArtifact->getFile()->getPathname(); + $artifactPath = $this->artifactFile->getPathname(); $this->filesystem->dumpFile($artifactPath, 'test'); @@ -100,11 +109,11 @@ public function revertDoesNothingIfBuildArtifactWasNotGenerated(): void } #[Framework\Attributes\Test] - public function revertRemovesDumpedBuildArtifact(): void + public function revertRemovesDumpedArtifact(): void { - $artifactPath = $this->buildArtifact->getFile()->getPathname(); + $artifactPath = $this->artifactFile->getPathname(); - $this->buildResult->setBuildArtifact($this->buildArtifact); + $this->buildResult->setArtifactFile($this->artifactFile); self::assertFileDoesNotExist($artifactPath); @@ -127,6 +136,6 @@ protected function tearDown(): void { parent::tearDown(); - $this->filesystem->remove($this->buildArtifact->getFile()->getPathname()); + $this->filesystem->remove($this->artifactFile->getPathname()); } } diff --git a/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php b/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php index 8edb41b8..8286eb2f 100644 --- a/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php +++ b/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php @@ -28,6 +28,7 @@ use Generator; use PHPUnit\Framework; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; /** * GenerateBuildArtifactStepTest. @@ -40,7 +41,7 @@ final class GenerateBuildArtifactStepTest extends Tests\ContainerAwareTestCase private Src\Builder\Generator\Step\GenerateBuildArtifactStep $subject; private Filesystem\Filesystem $filesystem; private Src\Builder\BuildResult $buildResult; - private string $artifactPath; + private Finder\SplFileInfo $artifactFile; protected function setUp(): void { @@ -49,25 +50,25 @@ protected function setUp(): void $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), ); - $this->artifactPath = Filesystem\Path::join( + $this->artifactFile = Src\Helper\FilesystemHelper::createFileObject( $this->buildResult->getWrittenDirectory(), '.build/build-artifact.json', ); } #[Framework\Attributes\Test] - #[Framework\Attributes\DataProvider('runAsksForConfirmationIfBuildArtifactPathAlreadyExistsDataProvider')] - public function runAsksForConfirmationIfBuildArtifactPathAlreadyExists(bool $continue, bool $expected): void + #[Framework\Attributes\DataProvider('runAsksForConfirmationIfArtifactPathAlreadyExistsDataProvider')] + public function runAsksForConfirmationIfArtifactPathAlreadyExists(bool $continue, bool $expected): void { self::$io->setUserInputs([$continue ? 'yes' : 'no']); - $this->filesystem->dumpFile($this->artifactPath, 'test'); + $this->filesystem->dumpFile($this->artifactFile->getPathname(), 'test'); - self::assertFileExists($this->artifactPath); + self::assertFileExists($this->artifactFile->getPathname()); self::assertSame($expected, $this->subject->run($this->buildResult)); self::assertSame(!$expected, $this->subject->isStopped()); self::assertFalse($this->buildResult->isStepApplied($this->subject)); - self::assertNull($this->buildResult->getBuildArtifact()); + self::assertNull($this->buildResult->getArtifactFile()); self::assertStringContainsString( 'The build artifact cannot be generated because the resulting file already exists.', self::$io->getOutput(), @@ -75,17 +76,17 @@ public function runAsksForConfirmationIfBuildArtifactPathAlreadyExists(bool $con } #[Framework\Attributes\Test] - public function runGeneratesBuildArtifact(): void + public function runGeneratesArtifact(): void { self::assertTrue($this->subject->run($this->buildResult)); - self::assertInstanceOf(Src\Builder\Artifact\BuildArtifact::class, $this->buildResult->getBuildArtifact()); + self::assertEquals($this->artifactFile, $this->buildResult->getArtifactFile()); self::assertTrue($this->buildResult->isStepApplied($this->subject)); } /** * @return Generator */ - public static function runAsksForConfirmationIfBuildArtifactPathAlreadyExistsDataProvider(): Generator + public static function runAsksForConfirmationIfArtifactPathAlreadyExistsDataProvider(): Generator { yield 'continue' => [true, true]; yield 'do not continue' => [false, false]; @@ -95,6 +96,6 @@ protected function tearDown(): void { parent::tearDown(); - $this->filesystem->remove($this->artifactPath); + $this->filesystem->remove($this->artifactFile->getPathname()); } } diff --git a/tests/src/Console/Command/CreateProjectCommandTest.php b/tests/src/Console/Command/CreateProjectCommandTest.php index 9d82a39e..bd7a4c5c 100644 --- a/tests/src/Console/Command/CreateProjectCommandTest.php +++ b/tests/src/Console/Command/CreateProjectCommandTest.php @@ -63,7 +63,6 @@ protected function setUp(): void $this->messenger, $configReader, new Src\Error\ErrorHandler($this->messenger), - $this->filesystem, [$this->templateProvider], ); $command->setApplication(new Console\Application()); diff --git a/tests/src/Exception/InvalidArtifactExceptionTest.php b/tests/src/Exception/InvalidArtifactExceptionTest.php new file mode 100644 index 00000000..faf65dc0 --- /dev/null +++ b/tests/src/Exception/InvalidArtifactExceptionTest.php @@ -0,0 +1,97 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Exception; + +use CPSIT\ProjectBuilder as Src; +use Opis\JsonSchema; +use PHPUnit\Framework; + +/** + * InvalidArtifactExceptionTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class InvalidArtifactExceptionTest extends Framework\TestCase +{ + #[Framework\Attributes\Test] + public function forFileReturnExceptionForFile(): void + { + $actual = Src\Exception\InvalidArtifactException::forFile('foo'); + + self::assertSame('The artifact file "foo" is invalid and cannot be processed.', $actual->getMessage()); + self::assertSame(1677141460, $actual->getCode()); + } + + #[Framework\Attributes\Test] + public function forPathReturnExceptionForPath(): void + { + $actual = Src\Exception\InvalidArtifactException::forPath('foo.baz'); + + self::assertSame('Invalid value at "foo.baz" in artifact.', $actual->getMessage()); + self::assertSame(1677140440, $actual->getCode()); + } + + #[Framework\Attributes\Test] + public function forInvalidVersionReturnExceptionForInvalidVersion(): void + { + $actual = Src\Exception\InvalidArtifactException::forInvalidVersion(); + + self::assertSame('Unable to detect a valid artifact version.', $actual->getMessage()); + self::assertSame(1677141758, $actual->getCode()); + } + + #[Framework\Attributes\Test] + public function forValidationErrorsReturnsExceptionForGivenValidationErrors(): void + { + $validationErrors = $this->generateValidationErrors(); + + $actual = Src\Exception\InvalidArtifactException::forValidationErrors($validationErrors); + + self::assertSame( + 'The artifact does not match the build artifact schema.'.PHP_EOL. + ' * Error at property path "/": The data (null) must match the type: object', + $actual->getMessage(), + ); + self::assertSame(1677601857, $actual->getCode()); + } + + private function generateValidationErrors(): JsonSchema\Errors\ValidationError + { + $schemaFile = dirname(__DIR__, 3).'/resources/build-artifact.schema.json'; + $schemaReference = 'file://'.$schemaFile; + + $resolver = new JsonSchema\Resolvers\SchemaResolver(); + $resolver->registerFile($schemaReference, $schemaFile); + + $validator = new JsonSchema\Validator(); + $validator->setResolver($resolver); + + $validationErrors = $validator->validate(null, $schemaReference)->error(); + + self::assertInstanceOf(JsonSchema\Errors\ValidationError::class, $validationErrors); + + return $validationErrors; + } +} diff --git a/tests/src/Exception/UnknownTemplateProviderExceptionTest.php b/tests/src/Exception/UnknownTemplateProviderExceptionTest.php new file mode 100644 index 00000000..6de52995 --- /dev/null +++ b/tests/src/Exception/UnknownTemplateProviderExceptionTest.php @@ -0,0 +1,45 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Exception; + +use CPSIT\ProjectBuilder as Src; +use PHPUnit\Framework; + +/** + * UnknownTemplateProviderExceptionTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class UnknownTemplateProviderExceptionTest extends Framework\TestCase +{ + #[Framework\Attributes\Test] + public function createReturnsExceptionForUnknownTemplateProvider(): void + { + $actual = Src\Exception\UnknownTemplateProviderException::create('foo'); + + self::assertSame('The template provider "foo" does not exist or is not available.', $actual->getMessage()); + self::assertSame(1677140774, $actual->getCode()); + } +} diff --git a/tests/src/Fixtures/DummyComposerProvider.php b/tests/src/Fixtures/DummyComposerProvider.php index cdb9f044..0a227631 100644 --- a/tests/src/Fixtures/DummyComposerProvider.php +++ b/tests/src/Fixtures/DummyComposerProvider.php @@ -75,9 +75,4 @@ public static function getType(): string { return self::TYPE; } - - public static function supports(string $type): bool - { - return self::TYPE === $type; - } } diff --git a/tests/src/Fixtures/DummyMigration.php b/tests/src/Fixtures/DummyMigration.php new file mode 100644 index 00000000..faffea39 --- /dev/null +++ b/tests/src/Fixtures/DummyMigration.php @@ -0,0 +1,61 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Fixtures; + +use CPSIT\ProjectBuilder\Builder; + +/** + * DummyMigration. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +final class DummyMigration extends Builder\Artifact\Migration\BaseMigration +{ + /** + * @var array{}|array{0: non-empty-string, 1?: non-empty-string|null, 2?: mixed} + */ + public array $remapArguments = []; + + public function migrate(array $artifact): array + { + if ([] !== $this->remapArguments) { + $this->remapValue($artifact, ...$this->remapArguments); + } + + return $artifact; + } + + public static function getSourceVersion(): int + { + return 1; + } + + public static function getTargetVersion(): int + { + return 2; + } +} diff --git a/tests/src/Fixtures/DummyProvider.php b/tests/src/Fixtures/DummyProvider.php index a8b60261..fd682d8d 100644 --- a/tests/src/Fixtures/DummyProvider.php +++ b/tests/src/Fixtures/DummyProvider.php @@ -90,9 +90,4 @@ public static function getType(): string { return self::TYPE; } - - public static function supports(string $type): bool - { - return self::TYPE === $type; - } } diff --git a/tests/src/Fixtures/Files/build-artifact.json b/tests/src/Fixtures/Files/build-artifact.json new file mode 100644 index 00000000..79bef3bc --- /dev/null +++ b/tests/src/Fixtures/Files/build-artifact.json @@ -0,0 +1,79 @@ +{ + "artifact": { + "version": 1, + "file": "foo.json", + "date": 1679501401 + }, + "template": { + "identifier": "cpsit/project-builder-template-json", + "hash": "faa1988f89bdbabe1852e60338b83588e6c593c8", + "package": { + "name": "cpsit/project-builder-test-template", + "version": "1.0.0.0", + "sourceReference": null, + "sourceUrl": null, + "distUrl": null + }, + "provider": { + "name": "Packagist.org", + "url": "https://packagist.org" + } + }, + "generator": { + "package": { + "name": "cpsit/project-builder", + "version": "1.7.3.0", + "sourceReference": null, + "sourceUrl": null, + "distUrl": null + }, + "executor": "composer" + }, + "result": { + "properties": { + "bar": { + "name": "Hello World" + } + }, + "steps": [ + { + "type": "collectBuildInstructions", + "applied": true + }, + { + "type": "processSourceFiles", + "applied": true + }, + { + "type": "processSharedSourceFiles", + "applied": true + }, + { + "type": "generateBuildArtifact", + "applied": true + }, + { + "type": "mirrorProcessedFiles", + "applied": true + } + ], + "processedFiles": [ + { + "source": "dummy.json.twig", + "target": "dummy.json" + }, + { + "source": "dummy-4.json", + "target": "overrides/dummy-4.json" + }, + { + "source": "shared-dummy.json.twig", + "target": "shared-dummy.json" + }, + { + "source": "shared-dummy-4.json", + "target": "overrides/shared-dummy-4.json" + } + ] + } +} diff --git a/tests/src/Fixtures/Files/invalid-artifact-version.json b/tests/src/Fixtures/Files/invalid-artifact-version.json new file mode 100644 index 00000000..1b4d69c1 --- /dev/null +++ b/tests/src/Fixtures/Files/invalid-artifact-version.json @@ -0,0 +1,5 @@ +{ + "artifact": { + "version": "foo" + } +} diff --git a/tests/src/Fixtures/Files/invalid-artifact.json b/tests/src/Fixtures/Files/invalid-artifact.json new file mode 100644 index 00000000..bef6eaa1 --- /dev/null +++ b/tests/src/Fixtures/Files/invalid-artifact.json @@ -0,0 +1,4 @@ +[ + "foo", + "baz" +] diff --git a/tests/src/Fixtures/Files/invalid-json.json b/tests/src/Fixtures/Files/invalid-json.json new file mode 100644 index 00000000..257cc564 --- /dev/null +++ b/tests/src/Fixtures/Files/invalid-json.json @@ -0,0 +1 @@ +foo diff --git a/tests/src/Fixtures/Files/test.schema.json b/tests/src/Fixtures/Files/test.schema.json new file mode 100644 index 00000000..5f0e02af --- /dev/null +++ b/tests/src/Fixtures/Files/test.schema.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://json-schema.org/draft/2019-09/schema#", + "type": "object", + "properties": { + "foo": { + "type": "string" + } + }, + "required": [ + "foo" + ] +} diff --git a/tests/src/Helper/ArrayHelperTest.php b/tests/src/Helper/ArrayHelperTest.php index 6bfdc17f..a3dba9ab 100644 --- a/tests/src/Helper/ArrayHelperTest.php +++ b/tests/src/Helper/ArrayHelperTest.php @@ -94,4 +94,44 @@ public function setValueByPathSetsValueAtGivenPath(): void $subject, ); } + + #[Framework\Attributes\Test] + public function removeByPathDoesNothingIfGivenPathDoesNotExist(): void + { + $subject = [ + 'foo' => [ + 'bar' => 'hello world!', + ], + ]; + + Src\Helper\ArrayHelper::removeByPath($subject, 'foo.hello.world'); + + self::assertSame( + [ + 'foo' => [ + 'bar' => 'hello world!', + ], + ], + $subject, + ); + } + + #[Framework\Attributes\Test] + public function removeByPathRemovesGivenPathInSubject(): void + { + $subject = [ + 'foo' => [ + 'bar' => 'hello world!', + ], + ]; + + Src\Helper\ArrayHelper::removeByPath($subject, 'foo.bar'); + + self::assertSame( + [ + 'foo' => [], + ], + $subject, + ); + } } diff --git a/tests/src/Helper/FilesystemHelperTest.php b/tests/src/Helper/FilesystemHelperTest.php index 7c7235a7..534a8955 100644 --- a/tests/src/Helper/FilesystemHelperTest.php +++ b/tests/src/Helper/FilesystemHelperTest.php @@ -182,4 +182,21 @@ public static function isDirectoryEmptyReturnsFalseIfDirectoryHasDirectoriesData yield 'normal directory' => ['foo']; yield 'dot-directory' => ['.foo']; } + + #[Framework\Attributes\Test] + public function resolveRelativePathReturnsGivenPathIfItIsAnAbsolutePath(): void + { + $path = '/foo/baz'; + + self::assertSame($path, Src\Helper\FilesystemHelper::resolveRelativePath($path)); + } + + #[Framework\Attributes\Test] + public function resolveRelativePathMakesRelativePathAbsolute(): void + { + $path = 'foo'; + $expected = dirname(__DIR__, 3).'/foo'; + + self::assertSame($expected, Src\Helper\FilesystemHelper::resolveRelativePath($path)); + } } diff --git a/tests/src/Json/SchemaValidatorTest.php b/tests/src/Json/SchemaValidatorTest.php new file mode 100644 index 00000000..dbbe7956 --- /dev/null +++ b/tests/src/Json/SchemaValidatorTest.php @@ -0,0 +1,65 @@ + + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +namespace CPSIT\ProjectBuilder\Tests\Json; + +use CPSIT\ProjectBuilder as Src; +use CPSIT\ProjectBuilder\Tests; +use Generator; +use PHPUnit\Framework; + +use function dirname; + +/** + * SchemaValidatorTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class SchemaValidatorTest extends Tests\ContainerAwareTestCase +{ + private Src\Json\SchemaValidator $subject; + + protected function setUp(): void + { + $this->subject = self::$container->get(Src\Json\SchemaValidator::class); + } + + #[Framework\Attributes\Test] + #[Framework\Attributes\DataProvider('validateValidatesJsonDataProvider')] + public function validateValidatesJson(mixed $data, bool $expected): void + { + $schemaFile = dirname(__DIR__).'/Fixtures/Files/test.schema.json'; + + self::assertSame($expected, $this->subject->validate($data, $schemaFile)->isValid()); + } + + /** + * @return Generator + */ + public static function validateValidatesJsonDataProvider(): Generator + { + yield 'valid json' => [(object) ['foo' => 'baz'], true]; + yield 'invalid json' => [null, false]; + } +} diff --git a/tests/src/Template/Provider/ProviderFactoryTest.php b/tests/src/Template/Provider/ProviderFactoryTest.php index d6c1ea01..cfc81ec5 100644 --- a/tests/src/Template/Provider/ProviderFactoryTest.php +++ b/tests/src/Template/Provider/ProviderFactoryTest.php @@ -45,9 +45,7 @@ protected function setUp(): void #[Framework\Attributes\Test] public function getThrowsExceptionIfNoProviderOfGivenTypeIsAvailable(): void { - $this->expectException(Src\Exception\UnsupportedTypeException::class); - $this->expectExceptionCode(1652800199); - $this->expectExceptionMessage('The type "foo" is not supported.'); + $this->expectExceptionObject(Src\Exception\UnknownTemplateProviderException::create('foo')); $this->subject->get('foo'); } @@ -68,4 +66,15 @@ public function getReturnsProviderOfGivenType(): void $this->subject->get('vcs'), ); } + + #[Framework\Attributes\Test] + public function getAllReturnsAllProviders(): void + { + $actual = $this->subject->getAll(); + + self::assertCount(3, $actual); + self::assertInstanceOf(Src\Template\Provider\PackagistProvider::class, $actual[0]); + self::assertInstanceOf(Src\Template\Provider\ComposerProvider::class, $actual[1]); + self::assertInstanceOf(Src\Template\Provider\VcsProvider::class, $actual[2]); + } }