From c07aa8a5c6c2467f437f376d41ba800ee159e261 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elias=20H=C3=A4u=C3=9Fler?= Date: Wed, 22 Mar 2023 15:58:57 +0100 Subject: [PATCH] [FEATURE] Implement concept for artifact migration --- config/services.php | 3 + config/services.yaml | 6 +- rector.php | 5 + resources/build-artifact.schema.json | 2 +- src/Builder/Artifact/Artifact.php | 31 ++- src/Builder/Artifact/BuildArtifact.php | 49 ++--- src/Builder/Artifact/GeneratorArtifact.php | 32 ++- .../Artifact/Migration/BaseVersion.php | 64 ++++++ src/Builder/Artifact/Migration/Version.php | 46 ++++ src/Builder/Artifact/Migration/Version1.php | 56 +++++ src/Builder/Artifact/PackageArtifact.php | 39 ++-- src/Builder/Artifact/ResultArtifact.php | 48 ++--- src/Builder/Artifact/TemplateArtifact.php | 44 ++-- src/Builder/ArtifactGenerator.php | 177 +++++++++++++++ src/Builder/ArtifactReader.php | 202 ++++++++++++++++++ src/Builder/BuildResult.php | 32 ++- src/Builder/Generator/Generator.php | 3 +- src/Builder/Generator/Step/CleanUpStep.php | 12 +- .../Generator/Step/DumpBuildArtifactStep.php | 11 +- .../Step/GenerateBuildArtifactStep.php | 9 +- src/Exception/InvalidArtifactException.php | 77 +++++++ src/Helper/ArrayHelper.php | 30 +++ tests/config/services.yaml | 3 + tests/src/Builder/Artifact/ArtifactTest.php | 111 ++++++++++ .../Builder/Artifact/BuildArtifactTest.php | 62 ++++++ .../Artifact/GeneratorArtifactTest.php | 70 ++++++ .../Artifact/Migration/BaseVersionTest.php | 135 ++++++++++++ .../Artifact/Migration/Version1Test.php | 67 ++++++ .../Builder/Artifact/PackageArtifactTest.php | 70 ++++++ .../Builder/Artifact/ResultArtifactTest.php | 79 +++++++ .../Builder/Artifact/TemplateArtifactTest.php | 77 +++++++ tests/src/Builder/ArtifactGeneratorTest.php | 134 ++++++++++++ tests/src/Builder/ArtifactReaderTest.php | 128 +++++++++++ tests/src/Builder/BuildResultTest.php | 32 ++- .../Generator/Step/CleanUpStepTest.php | 1 + .../Step/CollectBuildInstructionsStepTest.php | 2 + .../Step/DumpBuildArtifactStepTest.php | 45 ++-- .../Step/GenerateBuildArtifactStepTest.php | 24 ++- .../InstallComposerDependenciesStepTest.php | 1 + .../Step/ProcessSharedSourceFilesStepTest.php | 1 + .../Step/ProcessSourceFilesStepTest.php | 2 + .../Generator/Step/ShowNextStepsStepTest.php | 1 + .../src/Event/BuildStepProcessedEventTest.php | 1 + .../src/Event/BuildStepRevertedEventTest.php | 1 + .../Event/ProjectBuildFinishedEventTest.php | 1 + .../InvalidArtifactExceptionTest.php | 105 +++++++++ tests/src/Fixtures/DummyVersion.php | 61 ++++++ tests/src/Fixtures/Files/build-artifact.json | 79 +++++++ .../Files/invalid-artifact-version.json | 5 + .../src/Fixtures/Files/invalid-artifact.json | 4 + tests/src/Fixtures/Files/invalid-json.json | 1 + tests/src/Helper/ArrayHelperTest.php | 44 ++++ 52 files changed, 2137 insertions(+), 188 deletions(-) create mode 100644 src/Builder/Artifact/Migration/BaseVersion.php create mode 100644 src/Builder/Artifact/Migration/Version.php create mode 100644 src/Builder/Artifact/Migration/Version1.php create mode 100644 src/Builder/ArtifactGenerator.php create mode 100644 src/Builder/ArtifactReader.php create mode 100644 src/Exception/InvalidArtifactException.php create mode 100644 tests/src/Builder/Artifact/ArtifactTest.php create mode 100644 tests/src/Builder/Artifact/BuildArtifactTest.php create mode 100644 tests/src/Builder/Artifact/GeneratorArtifactTest.php create mode 100644 tests/src/Builder/Artifact/Migration/BaseVersionTest.php create mode 100644 tests/src/Builder/Artifact/Migration/Version1Test.php create mode 100644 tests/src/Builder/Artifact/PackageArtifactTest.php create mode 100644 tests/src/Builder/Artifact/ResultArtifactTest.php create mode 100644 tests/src/Builder/Artifact/TemplateArtifactTest.php create mode 100644 tests/src/Builder/ArtifactGeneratorTest.php create mode 100644 tests/src/Builder/ArtifactReaderTest.php create mode 100644 tests/src/Exception/InvalidArtifactExceptionTest.php create mode 100644 tests/src/Fixtures/DummyVersion.php create mode 100644 tests/src/Fixtures/Files/build-artifact.json create mode 100644 tests/src/Fixtures/Files/invalid-artifact-version.json create mode 100644 tests/src/Fixtures/Files/invalid-artifact.json create mode 100644 tests/src/Fixtures/Files/invalid-json.json diff --git a/config/services.php b/config/services.php index 87dd672d..5109ea12 100644 --- a/config/services.php +++ b/config/services.php @@ -43,6 +43,9 @@ DependencyInjection\Loader\Configurator\ContainerConfigurator $configurator, DependencyInjection\ContainerBuilder $container, ): void { + $container->registerForAutoconfiguration(Builder\Artifact\Migration\Version::class) + ->addTag('artifact.version_migration') + ; $container->registerForAutoconfiguration(Builder\Writer\WriterInterface::class) ->addTag('builder.writer') ; diff --git a/config/services.yaml b/config/services.yaml index 9378b1b5..c592fb38 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' @@ -20,6 +20,10 @@ services: - '../src/Resource/Local/ProcessedFile.php' - '../src/Template/TemplateSource.php' + CPSIT\ProjectBuilder\Builder\ArtifactReader: + arguments: + $versions: !tagged_iterator artifact.version_migration + CPSIT\ProjectBuilder\Builder\Config\Config: alias: 'app.config' diff --git a/rector.php b/rector.php index b3099f4b..7bfea8eb 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\Set\ValueObject\LevelSetList; @@ -37,6 +38,10 @@ __DIR__.'/tests/src/Fixtures/Templates/*/vendor/*', AddLiteralSeparatorToNumberRector::class, + JsonThrowOnErrorRector::class => [ + __DIR__.'/src/Builder/ArtifactGenerator.php', + __DIR__.'/src/Builder/ArtifactReader.php', + ], ]); $rectorConfig->phpVersion(PhpVersion::PHP_81); 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/BaseVersion.php b/src/Builder/Artifact/Migration/BaseVersion.php new file mode 100644 index 00000000..1215096d --- /dev/null +++ b/src/Builder/Artifact/Migration/BaseVersion.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; + +/** + * BaseVersion. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +abstract class BaseVersion implements Version +{ + /** + * @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/Version.php b/src/Builder/Artifact/Migration/Version.php new file mode 100644 index 00000000..2986fca9 --- /dev/null +++ b/src/Builder/Artifact/Migration/Version.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; + +/** + * Version. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +interface Version +{ + /** + * @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/Version1.php b/src/Builder/Artifact/Migration/Version1.php new file mode 100644 index 00000000..23c1ac87 --- /dev/null +++ b/src/Builder/Artifact/Migration/Version1.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; + +/** + * Version1. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class Version1 extends BaseVersion +{ + 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..59d6ea25 --- /dev/null +++ b/src/Builder/ArtifactGenerator.php @@ -0,0 +1,177 @@ + + * + * 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\Exception; +use CPSIT\ProjectBuilder\Json; +use CPSIT\ProjectBuilder\Paths; +use CPSIT\ProjectBuilder\Resource; +use Symfony\Component\Finder; + +use function array_map; +use function getenv; +use function json_decode; +use function json_encode; + +/** + * ArtifactGenerator. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +final class ArtifactGenerator +{ + public const VERSION = 1; + + public function __construct( + private readonly Json\SchemaValidator $schemaValidator, + ) { + } + + /** + * @throws Exception\InvalidArtifactException + */ + public function build( + Finder\SplFileInfo $file, + BuildResult $buildResult, + Package\RootPackageInterface $rootPackage, + int $version = self::VERSION, + ): Artifact\Artifact { + // Generate artifact + $artifact = new Artifact\Artifact( + $this->generateBuildArtifact($file, $version), + $this->generateTemplateArtifact($buildResult), + $this->generateGeneratorArtifact($rootPackage), + $this->generateResultArtifact($buildResult), + ); + + // Validate generated artifact + $json = json_decode((string) json_encode($artifact)); + $validationResult = $this->schemaValidator->validate($json, Paths::BUILD_ARTIFACT_SCHEMA); + + if (!$validationResult->isValid()) { + throw Exception\InvalidArtifactException::forValidationErrors($validationResult->error()); + } + + return $artifact; + } + + 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->getVersion(), + $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..a1e7be39 --- /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 Symfony\Component\Filesystem; + +use function array_filter; +use function file_get_contents; +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 $versions; + + /** + * @param iterable $versions + */ + public function __construct( + iterable $versions, + private readonly Filesystem\Filesystem $filesystem, + private readonly Json\SchemaValidator $schemaValidator, + ) { + $this->mapper = $this->createMapper(); + $this->versions = $this->orderVersions($versions); + } + + /** + * @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->versions as $version) { + if ($sourceVersion === $version::getSourceVersion()) { + $artifact = $version->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 $versions + * + * @return list + */ + private function orderVersions(iterable $versions): array + { + $prefixedVersions = []; + $orderedVersions = []; + + foreach ($versions as $version) { + $migrationPath = $version::getSourceVersion().'_'.$version::getTargetVersion(); + $prefixedVersions[$migrationPath] ??= []; + $prefixedVersions[$migrationPath][] = $version; + } + + ksort($prefixedVersions); + + foreach ($prefixedVersions as $prefixedVersionList) { + foreach ($prefixedVersionList as $version) { + $orderedVersions[] = $version; + } + } + + return $orderedVersions; + } +} diff --git a/src/Builder/BuildResult.php b/src/Builder/BuildResult.php index 6f9ec996..a04c3a1e 100644 --- a/src/Builder/BuildResult.php +++ b/src/Builder/BuildResult.php @@ -23,8 +23,10 @@ namespace CPSIT\ProjectBuilder\Builder; +use CPSIT\ProjectBuilder\Helper; use CPSIT\ProjectBuilder\Resource; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; use function array_values; @@ -37,7 +39,7 @@ final class BuildResult { private bool $mirrored = false; - private ?Artifact\BuildArtifact $buildArtifact = null; + private ?Finder\SplFileInfo $artifactFile = null; /** * @var array @@ -46,6 +48,7 @@ final class BuildResult public function __construct( private readonly BuildInstructions $instructions, + private readonly ArtifactGenerator $artifactGenerator, ) { } @@ -66,18 +69,30 @@ 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; } + public function getArtifact(): ?Artifact\Artifact + { + if (null !== $this->artifactFile) { + return $this->generateArtifact($this->artifactFile); + } + + return null; + } + /** * @return array */ @@ -134,4 +149,11 @@ public function getWrittenDirectory(): string return $this->instructions->getTemporaryDirectory(); } + + private function generateArtifact(Finder\SplFileInfo $artifactFile): Artifact\Artifact + { + $composer = Resource\Local\Composer::createComposer(Helper\FilesystemHelper::getProjectRootPath()); + + return $this->artifactGenerator->build($artifactFile, $this, $composer->getPackage()); + } } diff --git a/src/Builder/Generator/Generator.php b/src/Builder/Generator/Generator.php index a1449c64..dc6998cf 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, ) { } @@ -64,7 +65,7 @@ public function run(string $targetDirectory): Builder\BuildResult } $instructions = new Builder\BuildInstructions($this->config, $targetDirectory); - $result = new Builder\BuildResult($instructions); + $result = new Builder\BuildResult($instructions, $this->artifactGenerator); $this->eventDispatcher->dispatch(new Event\ProjectBuildStartedEvent($instructions)); diff --git a/src/Builder/Generator/Step/CleanUpStep.php b/src/Builder/Generator/Step/CleanUpStep.php index e2b5c4d4..ecdf70b0 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; } $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..0e60387f 100644 --- a/src/Builder/Generator/Step/DumpBuildArtifactStep.php +++ b/src/Builder/Generator/Step/DumpBuildArtifactStep.php @@ -47,21 +47,22 @@ public function __construct( public function run(Builder\BuildResult $buildResult): bool { - $buildArtifact = $buildResult->getBuildArtifact(); + $artifactFile = $buildResult->getArtifactFile(); + $artifact = $buildResult->getArtifact(); - if (null === $buildArtifact) { + if (null === $artifactFile || null === $artifact) { return true; } $buildResult->applyStep($this); - return $this->writer->write($buildArtifact->getFile(), $buildArtifact); + 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 a9fdab11..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::getProjectRootPath())->getPackage(), - ); - - $buildResult->setBuildArtifact($artifact); + $buildResult->setArtifactFile($artifactFile); $buildResult->applyStep($this); return true; 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/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/tests/config/services.yaml b/tests/config/services.yaml index 07f3abce..a2768108 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\DummyVersion: + tags: ['artifact.version_migration'] diff --git a/tests/src/Builder/Artifact/ArtifactTest.php b/tests/src/Builder/Artifact/ArtifactTest.php new file mode 100644 index 00000000..8d146b6d --- /dev/null +++ b/tests/src/Builder/Artifact/ArtifactTest.php @@ -0,0 +1,111 @@ + + * + * 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\TestCase; + +/** + * ArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ArtifactTest extends 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, + ); + } + + /** + * @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..1c24a916 --- /dev/null +++ b/tests/src/Builder/Artifact/BuildArtifactTest.php @@ -0,0 +1,62 @@ + + * + * 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\TestCase; + +use function json_encode; + +/** + * BuildArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class BuildArtifactTest extends TestCase +{ + private Src\Builder\Artifact\BuildArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\BuildArtifact(1, 'file', 123); + } + + /** + * @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..ffe9a330 --- /dev/null +++ b/tests/src/Builder/Artifact/GeneratorArtifactTest.php @@ -0,0 +1,70 @@ + + * + * 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\TestCase; + +use function json_encode; + +/** + * GeneratorArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class GeneratorArtifactTest extends 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', + ); + } + + /** + * @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/BaseVersionTest.php b/tests/src/Builder/Artifact/Migration/BaseVersionTest.php new file mode 100644 index 00000000..399423aa --- /dev/null +++ b/tests/src/Builder/Artifact/Migration/BaseVersionTest.php @@ -0,0 +1,135 @@ + + * + * 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\TestCase; + +use function strrev; + +/** + * BaseVersionTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class BaseVersionTest extends TestCase +{ + private Tests\Fixtures\DummyVersion $subject; + + /** + * @var array + */ + private array $artifact; + + protected function setUp(): void + { + $this->subject = new Tests\Fixtures\DummyVersion(); + $this->artifact = [ + 'foo' => [ + 'baz' => 'hello world', + ], + 'baz' => 'dummy', + ]; + } + + /** + * @test + */ + public function remapValueCanRemapPathToOtherPath(): void + { + $this->subject->remapArguments = [ + 'foo.baz', + 'baz', + ]; + + $expected = [ + 'foo' => [], + 'baz' => 'hello world', + ]; + + self::assertSame($expected, $this->subject->migrate($this->artifact)); + } + + /** + * @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)); + } + + /** + * @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)); + } + + /** + * @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/Version1Test.php b/tests/src/Builder/Artifact/Migration/Version1Test.php new file mode 100644 index 00000000..97ef9f2d --- /dev/null +++ b/tests/src/Builder/Artifact/Migration/Version1Test.php @@ -0,0 +1,67 @@ + + * + * 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\TestCase; + +/** + * Version1Test. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class Version1Test extends TestCase +{ + private Src\Builder\Artifact\Migration\Version1 $subject; + + /** + * @var array + */ + private array $artifact; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\Migration\Version1(); + $this->artifact = [ + 'artifact' => [ + 'file' => 'foo', + ], + ]; + } + + /** + * @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..e8118a03 --- /dev/null +++ b/tests/src/Builder/Artifact/PackageArtifactTest.php @@ -0,0 +1,70 @@ + + * + * 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\TestCase; + +use function json_encode; + +/** + * PackageArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class PackageArtifactTest extends TestCase +{ + private Src\Builder\Artifact\PackageArtifact $subject; + + protected function setUp(): void + { + $this->subject = new Src\Builder\Artifact\PackageArtifact( + 'name', + 'version', + 'sourceReference', + 'sourceUrl', + 'distUrl', + ); + } + + /** + * @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..a66ca473 --- /dev/null +++ b/tests/src/Builder/Artifact/ResultArtifactTest.php @@ -0,0 +1,79 @@ + + * + * 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\TestCase; + +use function json_encode; + +/** + * ResultArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class ResultArtifactTest extends 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', + ], + ], + ); + } + + /** + * @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..d3552082 --- /dev/null +++ b/tests/src/Builder/Artifact/TemplateArtifactTest.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\TestCase; + +use function json_encode; + +/** + * TemplateArtifactTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class TemplateArtifactTest extends 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', + ], + ); + } + + /** + * @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..aabd7b43 --- /dev/null +++ b/tests/src/Builder/ArtifactGeneratorTest.php @@ -0,0 +1,134 @@ + + * + * 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 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->subject, + ); + $this->rootPackage = Src\Resource\Local\Composer::createComposer(dirname(__DIR__, 3))->getPackage(); + } + + /** + * @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..cc6552d8 --- /dev/null +++ b/tests/src/Builder/ArtifactReaderTest.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; + +use CPSIT\ProjectBuilder as Src; +use CPSIT\ProjectBuilder\Tests; + +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\DummyVersion $version; + private Src\Builder\ArtifactReader $subject; + + protected function setUp(): void + { + $this->artifactFile = dirname(__DIR__).'/Fixtures/Files/build-artifact.json'; + $this->version = self::$container->get(Tests\Fixtures\DummyVersion::class); + $this->subject = self::$container->get(Src\Builder\ArtifactReader::class); + } + + /** + * @test + */ + public function fromFileThrowsExceptionIfFileIsNotReadable(): void + { + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile('foo')); + + $this->subject->fromFile('foo'); + } + + /** + * @test + */ + public function fromFileThrowsExceptionIfJsonIsInvalid(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-json.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile($file)); + + $this->subject->fromFile($file); + } + + /** + * @test + */ + public function fromFileThrowsExceptionIfJsonIsUnsupported(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-artifact.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forFile($file)); + + $this->subject->fromFile($file); + } + + /** + * @test + */ + public function fromFileThrowsExceptionIfArtifactVersionIsInvalid(): void + { + $file = dirname(__DIR__).'/Fixtures/Files/invalid-artifact-version.json'; + + $this->expectExceptionObject(Src\Exception\InvalidArtifactException::forInvalidVersion()); + + $this->subject->fromFile($file); + } + + /** + * @test + */ + public function fromFileThrowsExceptionIfMigratedArtifactIsInvalid(): void + { + $this->version->remapArguments = [ + 'artifact', + null, + 'foo', + ]; + + $this->expectException(Src\Exception\InvalidArtifactException::class); + $this->expectExceptionCode(1677601857); + + $this->subject->fromFile($this->artifactFile); + } + + /** + * @test + */ + public function fromFilePerformsMigrations(): void + { + $this->version->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 07a92639..3b9e2412 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 Symfony\Component\Finder; @@ -48,7 +47,10 @@ 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, + self::$container->get(Src\Builder\ArtifactGenerator::class), + ); } /** @@ -71,17 +73,27 @@ public function isMirroredReturnsMirrorState(): void /** * @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($artifactFile, $this->subject->setArtifactFile($artifactFile)->getArtifactFile()); + } + + /** + * @test + */ + public function getArtifactReturnsArtifact(): void + { + self::assertNull($this->subject->getArtifact()); + + $artifactFile = Src\Helper\FilesystemHelper::createFileObject('/foo', 'baz'); + + $this->subject->setArtifactFile($artifactFile); - self::assertSame($buildArtifact, $this->subject->setBuildArtifact($buildArtifact)->getBuildArtifact()); + self::assertInstanceOf(Src\Builder\Artifact\Artifact::class, $this->subject->getArtifact()); } /** diff --git a/tests/src/Builder/Generator/Step/CleanUpStepTest.php b/tests/src/Builder/Generator/Step/CleanUpStepTest.php index e6349c77..3d8dbb53 100644 --- a/tests/src/Builder/Generator/Step/CleanUpStepTest.php +++ b/tests/src/Builder/Generator/Step/CleanUpStepTest.php @@ -49,6 +49,7 @@ protected function setUp(): void self::$config, Src\Helper\FilesystemHelper::getNewTemporaryDirectory(), ), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); } diff --git a/tests/src/Builder/Generator/Step/CollectBuildInstructionsStepTest.php b/tests/src/Builder/Generator/Step/CollectBuildInstructionsStepTest.php index 30872868..0dcdd5af 100644 --- a/tests/src/Builder/Generator/Step/CollectBuildInstructionsStepTest.php +++ b/tests/src/Builder/Generator/Step/CollectBuildInstructionsStepTest.php @@ -66,6 +66,7 @@ public function runAppliesNullAsDefaultValueOnSkippedProperties(): void $buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions($config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject->run($buildResult); @@ -107,6 +108,7 @@ public function runAppliesNullAsDefaultValueOnSkippedSubProperties(): void $buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions($config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject->run($buildResult); diff --git a/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php b/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php index 90b86fdd..3483dfae 100644 --- a/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php +++ b/tests/src/Builder/Generator/Step/DumpBuildArtifactStepTest.php @@ -27,7 +27,9 @@ use CPSIT\ProjectBuilder as Src; use CPSIT\ProjectBuilder\Tests; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; +use function dirname; use function json_encode; /** @@ -40,8 +42,10 @@ 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 { @@ -50,48 +54,53 @@ protected function setUp(): void $this->filesystem, self::$container->get(Src\Builder\Writer\JsonFileWriter::class), ); + $this->artifactGenerator = self::$container->get(Src\Builder\ArtifactGenerator::class); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + $this->artifactGenerator, ); - $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(); } /** * @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()); } /** * @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), ); } /** * @test */ - public function revertDoesNothingIfBuildArtifactWasNotGenerated(): void + public function revertDoesNothingIfArtifactWasNotGenerated(): void { - $artifactPath = $this->buildArtifact->getFile()->getPathname(); + $artifactPath = $this->artifactFile->getPathname(); $this->filesystem->dumpFile($artifactPath, 'test'); @@ -107,11 +116,11 @@ public function revertDoesNothingIfBuildArtifactWasNotGenerated(): void /** * @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); @@ -136,6 +145,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 9da614e8..656150ee 100644 --- a/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php +++ b/tests/src/Builder/Generator/Step/GenerateBuildArtifactStepTest.php @@ -27,6 +27,7 @@ use CPSIT\ProjectBuilder\Tests; use Generator; use Symfony\Component\Filesystem; +use Symfony\Component\Finder; /** * GenerateBuildArtifactStepTest. @@ -39,7 +40,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 { @@ -47,8 +48,9 @@ protected function setUp(): void $this->filesystem = self::$container->get(Filesystem\Filesystem::class); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); - $this->artifactPath = Filesystem\Path::join( + $this->artifactFile = Src\Helper\FilesystemHelper::createFileObject( $this->buildResult->getWrittenDirectory(), '.build/build-artifact.json', ); @@ -57,19 +59,19 @@ protected function setUp(): void /** * @test * - * @dataProvider runAsksForConfirmationIfBuildArtifactPathAlreadyExistsDataProvider + * @dataProvider runAsksForConfirmationIfArtifactPathAlreadyExistsDataProvider */ - public function runAsksForConfirmationIfBuildArtifactPathAlreadyExists(bool $continue, bool $expected): void + 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->getArtifact()); self::assertStringContainsString( 'The build artifact cannot be generated because the resulting file already exists.', self::$io->getOutput(), @@ -79,17 +81,17 @@ public function runAsksForConfirmationIfBuildArtifactPathAlreadyExists(bool $con /** * @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::assertInstanceOf(Src\Builder\Artifact\Artifact::class, $this->buildResult->getArtifact()); self::assertTrue($this->buildResult->isStepApplied($this->subject)); } /** * @return Generator */ - public function runAsksForConfirmationIfBuildArtifactPathAlreadyExistsDataProvider(): Generator + public function runAsksForConfirmationIfArtifactPathAlreadyExistsDataProvider(): Generator { yield 'continue' => [true, true]; yield 'do not continue' => [false, false]; @@ -99,6 +101,6 @@ protected function tearDown(): void { parent::tearDown(); - $this->filesystem->remove($this->artifactPath); + $this->filesystem->remove($this->artifactFile->getPathname()); } } diff --git a/tests/src/Builder/Generator/Step/InstallComposerDependenciesStepTest.php b/tests/src/Builder/Generator/Step/InstallComposerDependenciesStepTest.php index 2ee60a16..354bb878 100644 --- a/tests/src/Builder/Generator/Step/InstallComposerDependenciesStepTest.php +++ b/tests/src/Builder/Generator/Step/InstallComposerDependenciesStepTest.php @@ -49,6 +49,7 @@ protected function setUp(): void $this->subject = self::$container->get(Src\Builder\Generator\Step\InstallComposerDependenciesStep::class); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); } diff --git a/tests/src/Builder/Generator/Step/ProcessSharedSourceFilesStepTest.php b/tests/src/Builder/Generator/Step/ProcessSharedSourceFilesStepTest.php index d5a6e392..71c9cd22 100644 --- a/tests/src/Builder/Generator/Step/ProcessSharedSourceFilesStepTest.php +++ b/tests/src/Builder/Generator/Step/ProcessSharedSourceFilesStepTest.php @@ -45,6 +45,7 @@ protected function setUp(): void $this->subject->setConfig($step); $this->result = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); } diff --git a/tests/src/Builder/Generator/Step/ProcessSourceFilesStepTest.php b/tests/src/Builder/Generator/Step/ProcessSourceFilesStepTest.php index ece7c0bc..5b5e3f90 100644 --- a/tests/src/Builder/Generator/Step/ProcessSourceFilesStepTest.php +++ b/tests/src/Builder/Generator/Step/ProcessSourceFilesStepTest.php @@ -48,6 +48,7 @@ protected function setUp(): void $this->subject->setConfig($step); $this->result = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); } @@ -93,6 +94,7 @@ public function runCanProcessTheSameSourceFileWithMultipleConditions( $result = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions($config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject->setConfig($step); diff --git a/tests/src/Builder/Generator/Step/ShowNextStepsStepTest.php b/tests/src/Builder/Generator/Step/ShowNextStepsStepTest.php index fb63bacd..7bff1e83 100644 --- a/tests/src/Builder/Generator/Step/ShowNextStepsStepTest.php +++ b/tests/src/Builder/Generator/Step/ShowNextStepsStepTest.php @@ -44,6 +44,7 @@ protected function setUp(): void $this->subject = self::$container->get(Src\Builder\Generator\Step\ShowNextStepsStep::class); $this->result = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); } diff --git a/tests/src/Event/BuildStepProcessedEventTest.php b/tests/src/Event/BuildStepProcessedEventTest.php index ca4e5af5..4e126d7b 100644 --- a/tests/src/Event/BuildStepProcessedEventTest.php +++ b/tests/src/Event/BuildStepProcessedEventTest.php @@ -43,6 +43,7 @@ protected function setUp(): void $this->step = new Tests\Fixtures\DummyStep(); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject = new Src\Event\BuildStepProcessedEvent( $this->step, diff --git a/tests/src/Event/BuildStepRevertedEventTest.php b/tests/src/Event/BuildStepRevertedEventTest.php index 9e6e4df6..c9eb7537 100644 --- a/tests/src/Event/BuildStepRevertedEventTest.php +++ b/tests/src/Event/BuildStepRevertedEventTest.php @@ -43,6 +43,7 @@ protected function setUp(): void $this->step = new Tests\Fixtures\DummyStep(); $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject = new Src\Event\BuildStepRevertedEvent( $this->step, diff --git a/tests/src/Event/ProjectBuildFinishedEventTest.php b/tests/src/Event/ProjectBuildFinishedEventTest.php index b355ed50..9a0bcf2f 100644 --- a/tests/src/Event/ProjectBuildFinishedEventTest.php +++ b/tests/src/Event/ProjectBuildFinishedEventTest.php @@ -41,6 +41,7 @@ protected function setUp(): void { $this->buildResult = new Src\Builder\BuildResult( new Src\Builder\BuildInstructions(self::$config, 'foo'), + self::$container->get(Src\Builder\ArtifactGenerator::class), ); $this->subject = new Src\Event\ProjectBuildFinishedEvent( $this->buildResult, diff --git a/tests/src/Exception/InvalidArtifactExceptionTest.php b/tests/src/Exception/InvalidArtifactExceptionTest.php new file mode 100644 index 00000000..7e181558 --- /dev/null +++ b/tests/src/Exception/InvalidArtifactExceptionTest.php @@ -0,0 +1,105 @@ + + * + * 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\TestCase; + +/** + * InvalidArtifactExceptionTest. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + */ +final class InvalidArtifactExceptionTest extends TestCase +{ + /** + * @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()); + } + + /** + * @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()); + } + + /** + * @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()); + } + + /** + * @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/Fixtures/DummyVersion.php b/tests/src/Fixtures/DummyVersion.php new file mode 100644 index 00000000..a100f7c1 --- /dev/null +++ b/tests/src/Fixtures/DummyVersion.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; + +/** + * DummyVersion. + * + * @author Elias Häußler + * @license GPL-3.0-or-later + * + * @internal + */ +final class DummyVersion extends Builder\Artifact\Migration\BaseVersion +{ + /** + * @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/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/Helper/ArrayHelperTest.php b/tests/src/Helper/ArrayHelperTest.php index 74e6612e..b3c43352 100644 --- a/tests/src/Helper/ArrayHelperTest.php +++ b/tests/src/Helper/ArrayHelperTest.php @@ -98,4 +98,48 @@ public function setValueByPathSetsValueAtGivenPath(): void $subject, ); } + + /** + * @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, + ); + } + + /** + * @test + */ + public function removeByPathRemovesGivenPathInSubject(): void + { + $subject = [ + 'foo' => [ + 'bar' => 'hello world!', + ], + ]; + + Src\Helper\ArrayHelper::removeByPath($subject, 'foo.bar'); + + self::assertSame( + [ + 'foo' => [], + ], + $subject, + ); + } }