From 210c36dca7d7a836861e57886f3fdbd9d28922a6 Mon Sep 17 00:00:00 2001 From: Benjamin Kott Date: Tue, 26 May 2020 13:50:52 +0200 Subject: [PATCH] [TASK] Initialize --- .editorconfig | 19 ++ .gitattributes | 20 ++ .github/workflows/ci.yml | 65 ++++++ .gitignore | 5 + .php_cs.dist | 69 ++++++ .phplint.yml | 9 + LICENSE | 21 ++ README.md | 40 ++++ composer.json | 73 ++++++ phpunit.xml.dist | 24 ++ src/Configuration/File.php | 32 +++ src/Configuration/GitignoreEntry.php | 25 +++ src/Configuration/InstallerConfiguration.php | 51 +++++ src/Factory/ConfigurationFactory.php | 39 ++++ src/Handler/AbstractHandler.php | 38 ++++ src/Handler/FileHandler.php | 54 +++++ src/Handler/GitignoreHandler.php | 40 ++++ src/Installer/ConfigurationInstaller.php | 94 ++++++++ src/Installer/Plugin.php | 37 +++ src/Service/GitignoreService.php | 110 +++++++++ src/bootstrap.php | 21 ++ .../Installer/ConfigurationInstallerTest.php | 210 ++++++++++++++++++ tests/Installer/InstallerTestCase.php | 173 +++++++++++++++ tests/TestCase.php | 31 +++ tests/bootstrap.php | 11 + 25 files changed, 1311 insertions(+) create mode 100644 .editorconfig create mode 100644 .gitattributes create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .php_cs.dist create mode 100644 .phplint.yml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 composer.json create mode 100644 phpunit.xml.dist create mode 100644 src/Configuration/File.php create mode 100644 src/Configuration/GitignoreEntry.php create mode 100644 src/Configuration/InstallerConfiguration.php create mode 100644 src/Factory/ConfigurationFactory.php create mode 100644 src/Handler/AbstractHandler.php create mode 100644 src/Handler/FileHandler.php create mode 100644 src/Handler/GitignoreHandler.php create mode 100644 src/Installer/ConfigurationInstaller.php create mode 100644 src/Installer/Plugin.php create mode 100644 src/Service/GitignoreService.php create mode 100644 src/bootstrap.php create mode 100644 tests/Installer/ConfigurationInstallerTest.php create mode 100644 tests/Installer/InstallerTestCase.php create mode 100644 tests/TestCase.php create mode 100644 tests/bootstrap.php diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..59343a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.yml] +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e76bf01 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,20 @@ +# Folders +/.github export-ignore +/tests export-ignore + +# Files +.editorconfig export-ignore +.gitattributes export-ignore +.gitignore export-ignore +.php_cs.dist export-ignore +.phplint.yml export-ignore +phpunit.xml.dist export-ignore + +# Enforce checkout with linux lf consistent over all plattforms +.php_cs.dist text eol=lf +*.json text eol=lf +*.md text eol=lf +*.php text eol=lf +*.xml text eol=lf +*.xml.dist text eol=lf +*.yml text eol=lf diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..cfd2afd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI + +on: [push, pull_request] + +jobs: + build-php: + name: PHP ${{ matrix.php-versions }} with Composer ${{ matrix.composer-versions }} on ${{ matrix.operating-system }} + runs-on: ${{ matrix.operating-system }} + strategy: + max-parallel: 4 + matrix: + operating-system: + - 'ubuntu-latest' + - 'windows-latest' + php-versions: + - '7.3' + - '7.4' + composer-versions: + - 'v1' + - 'v2' + steps: + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Set up PHP Version + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: composer:${{ matrix.composer-versions }} + + - name: Environment Check + run: | + php --version + composer --version + + - name: Require Composer@v1 + if: ${{ matrix.composer-versions == 'v1' }} + run: | + composer require "composer/composer:^1.10" --dev --no-update + + - name: Require Composer@v2 + if: ${{ matrix.composer-versions == 'v2' }} + run: | + composer require "composer/composer:2.0.x-dev" --dev --no-update + + - name: Install + run: | + composer install --no-progress + + - name: Info + run: | + composer info + + - name: Lint + run: | + composer test:php:lint + + - name: CGL + run: | + composer cgl + + - name: Unit Tests + run: | + composer test:php:unit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae29318 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +bin +build +vendor +*.cache +composer.lock diff --git a/.php_cs.dist b/.php_cs.dist new file mode 100644 index 0000000..0f4f89d --- /dev/null +++ b/.php_cs.dist @@ -0,0 +1,69 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR2' => true, + 'declare_strict_types' => true, + 'header_comment' => [ + 'header' => $header + ], + 'general_phpdoc_annotation_remove' => [ + 'annotations' => [ + 'author' + ] + ], + 'no_leading_import_slash' => true, + 'no_trailing_comma_in_singleline_array' => true, + 'no_singleline_whitespace_before_semicolons' => true, + 'no_unused_imports' => true, + 'concat_space' => ['spacing' => 'one'], + 'no_whitespace_in_blank_line' => true, + 'ordered_imports' => true, + 'single_quote' => true, + 'no_empty_statement' => true, + 'no_extra_blank_lines' => true, + 'phpdoc_no_package' => true, + 'phpdoc_scalar' => true, + 'no_blank_lines_after_phpdoc' => true, + 'array_syntax' => ['syntax' => 'short'], + 'whitespace_after_comma_in_array' => true, + 'function_typehint_space' => true, + 'single_line_comment_style' => true, + 'no_alias_functions' => true, + 'lowercase_cast' => true, + 'no_leading_namespace_whitespace' => true, + 'native_function_casing' => true, + 'self_accessor' => true, + 'no_short_bool_cast' => true, + 'no_unneeded_control_parentheses' => true + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(['src', 'tests']) + ); diff --git a/.phplint.yml b/.phplint.yml new file mode 100644 index 0000000..6a81694 --- /dev/null +++ b/.phplint.yml @@ -0,0 +1,9 @@ +path: ./ +jobs: 10 +cache: .phplint.cache +extensions: + - php +exclude: + - bin + - build + - vendor diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b3343da --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 Benjamin Kott, http://www.bk2k.info + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc9471c --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Project Configuration Installer + +Installer for Configuration Packages. +Example: https://github.com/benjaminkott/config-phpcsfixer-typo3 + +## Build your own configuration package + +Adapt the `composer.json` of your **configuration package**. + +1. Ensure the type is set to `project-configuration`. +1. Ensure `bk2k/configuration-installer` is required in any version. + +```json +{ + "type": "project-configuration", + "require": { + "bk2k/configuration-installer": "*" + } +} + ``` + +### Add a manifest to your configuration package root. + +The `manifest.json` file instructs the installer. + +1. It defines which `files` should be copied to your project +1. It defines which `gitignore` entries will be added to your projects .gitignore file. + + +```json +{ + "files": { + ".php_cs.dist": ".php_cs.dist" + }, + "gitignore": [ + "/.php_cs.dist", + "/.php_cs.cache" + ] +} +``` diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c32f68f --- /dev/null +++ b/composer.json @@ -0,0 +1,73 @@ +{ + "name": "bk2k/configuration-installer", + "type": "composer-plugin", + "description": "Configuration Installer", + "homepage": "https://github.com/benjaminkott/configuration-installer", + "authors": [ + { + "name": "Benjamin Kott", + "email": "info@bk2k.info", + "role": "Developer", + "homepage": "http://www.bk2k.info/" + } + ], + "support": { + "issues": "https://github.com/benjaminkott/configuration-installer/issues" + }, + "minimum-stability": "dev", + "prefer-stable": true, + "license": "MIT", + "require": { + "php": "^7.3", + "composer-plugin-api": "^1.1 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.10 || ^2.0@dev", + "friendsofphp/php-cs-fixer": "^2.16 || ^3.0@dev", + "overtrue/phplint": "^2.0", + "phpunit/phpunit": "^9.2" + }, + "autoload": { + "psr-4": { + "BK2K\\ConfigurationInstaller\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "BK2K\\ConfigurationInstallerTest\\": "tests/" + } + }, + "config": { + "bin-dir": "bin", + "optimize-autoloader": true, + "sort-packages": true, + "vendor-dir": "vendor" + }, + "scripts": { + "test:php:lint": [ + "phplint" + ], + "test:php:unit": [ + "Composer\\Config::disableProcessTimeout", + "phpunit" + ], + "test:php:cover": [ + "Composer\\Config::disableProcessTimeout", + "phpunit --coverage-html build/coverage-report" + ], + "test": [ + "@cgl", + "@test:php:lint", + "@test:php:unit" + ], + "cgl:fix": [ + "php-cs-fixer fix" + ], + "cgl": [ + "php-cs-fixer fix --dry-run" + ] + }, + "extra": { + "class": "BK2K\\ConfigurationInstaller\\Installer\\Plugin" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..40b2590 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + tests + + + + + src + + + + diff --git a/src/Configuration/File.php b/src/Configuration/File.php new file mode 100644 index 0000000..ae56ae4 --- /dev/null +++ b/src/Configuration/File.php @@ -0,0 +1,32 @@ +source = $source; + $this->target = $target; + } + + public function getSource(): string + { + return $this->source; + } + + public function getTarget():string + { + return $this->target; + } +} diff --git a/src/Configuration/GitignoreEntry.php b/src/Configuration/GitignoreEntry.php new file mode 100644 index 0000000..50381f7 --- /dev/null +++ b/src/Configuration/GitignoreEntry.php @@ -0,0 +1,25 @@ +path = $path; + } + + public function getPath(): string + { + return $this->path; + } +} diff --git a/src/Configuration/InstallerConfiguration.php b/src/Configuration/InstallerConfiguration.php new file mode 100644 index 0000000..957296d --- /dev/null +++ b/src/Configuration/InstallerConfiguration.php @@ -0,0 +1,51 @@ +files[] = $file; + return $this; + } + + /** + * @return File[] + */ + public function getFiles(): array + { + return $this->files; + } + + public function addGitignoreEntry(GitignoreEntry $gitignoreEntry): self + { + $this->gitignoreEntries[] = $gitignoreEntry; + return $this; + } + + /** + * @return GitignoreEntry[] + */ + public function getGitignoreEntries(): array + { + return $this->gitignoreEntries; + } +} diff --git a/src/Factory/ConfigurationFactory.php b/src/Factory/ConfigurationFactory.php new file mode 100644 index 0000000..a9e9c11 --- /dev/null +++ b/src/Factory/ConfigurationFactory.php @@ -0,0 +1,39 @@ + $target) { + if (!is_string($target)) { + continue; + } + $source = is_int($source) ? $target : $source; + $installerConfiguration->addFile(new File($source, $target)); + } + foreach ($data['gitignore'] ?? [] as $value) { + $installerConfiguration->addGitignoreEntry(new GitignoreEntry($value)); + } + return $installerConfiguration; + } +} diff --git a/src/Handler/AbstractHandler.php b/src/Handler/AbstractHandler.php new file mode 100644 index 0000000..d0e1c92 --- /dev/null +++ b/src/Handler/AbstractHandler.php @@ -0,0 +1,38 @@ +composer = $composer; + $this->io = $io; + $this->filesystem = $filesystem; + $this->initialize(); + } + + public function initialize() + { + } + + abstract public function install(InstallerConfiguration $installerConfiguration, PackageInterface $package); + abstract public function remove(InstallerConfiguration $installerConfiguration, PackageInterface $package); +} diff --git a/src/Handler/FileHandler.php b/src/Handler/FileHandler.php new file mode 100644 index 0000000..b2ef2b3 --- /dev/null +++ b/src/Handler/FileHandler.php @@ -0,0 +1,54 @@ +io->write(' Installing file links for ' . $package->getName() . ''); + $packagePath = $this->getPackagePath($package); + foreach ($installerConfiguration->getFiles() as $file) { + $source = $this->filesystem->normalizePath($packagePath . '/' . $file->getSource()); + $target = $this->filesystem->normalizePath(getcwd() . '/' . $file->getTarget()); + if (!file_exists($source)) { + throw new \InvalidArgumentException('The source "' . $source . '" is not available.'); + } + if (file_exists($target)) { + $this->filesystem->remove($target); + } + $this->filesystem->ensureDirectoryExists(dirname($target)); + $this->filesystem->copy($source, $target); + $this->io->write(' ' . $source . ' -> ' . $target . ''); + } + } + + public function remove(InstallerConfiguration $installerConfiguration, PackageInterface $package) + { + $this->io->write(' Remove file links for ' . $package->getName() . ''); + foreach ($installerConfiguration->getFiles() as $file) { + $target = $this->filesystem->normalizePath(realpath($file->getTarget())); + $this->io->write(' Remove ' . $target . ''); + $this->filesystem->unlink($target); + } + } + + private function getPackagePath(PackageInterface $package): string + { + $vendorDir = rtrim($this->composer->getConfig()->get('vendor-dir'), '/'); + $basePath = ($vendorDir ? $vendorDir . '/' : '') . $package->getPrettyName(); + $targetDir = $package->getTargetDir(); + + return $basePath . ($targetDir ? '/' . $targetDir : ''); + } +} diff --git a/src/Handler/GitignoreHandler.php b/src/Handler/GitignoreHandler.php new file mode 100644 index 0000000..2b3d1ed --- /dev/null +++ b/src/Handler/GitignoreHandler.php @@ -0,0 +1,40 @@ +gitignore = new GitignoreService(); + } + + public function install(InstallerConfiguration $installerConfiguration, PackageInterface $package) + { + foreach ($installerConfiguration->getGitignoreEntries() as $entry) { + $this->gitignore->addEntry($entry->getPath()); + } + $this->gitignore->write(); + } + + public function remove(InstallerConfiguration $installerConfiguration, PackageInterface $package) + { + foreach ($installerConfiguration->getGitignoreEntries() as $entry) { + $this->gitignore->removeEntry($entry->getPath()); + } + $this->gitignore->write(); + } +} diff --git a/src/Installer/ConfigurationInstaller.php b/src/Installer/ConfigurationInstaller.php new file mode 100644 index 0000000..d3a7161 --- /dev/null +++ b/src/Installer/ConfigurationInstaller.php @@ -0,0 +1,94 @@ +handler = [ + 'files' => Handler\FileHandler::class, + 'gitignore' => Handler\GitignoreHandler::class + ]; + } + + public function getHandler($key) + { + if (!isset($this->handler[$key])) { + throw new \InvalidArgumentException(sprintf('Unknown handler "%s".', $key)); + } + if (isset($this->cache[$key])) { + return $this->cache[$key]; + } + $class = $this->handler[$key]; + return $this->cache[$key] = new $class($this->composer, $this->io, $this->filesystem); + } + + protected function installCode(PackageInterface $package) + { + parent::installCode($package); + $this->processInstallPackage($package); + } + + protected function updateCode(PackageInterface $initial, PackageInterface $target) + { + $this->processRemovePackage($initial); + parent::updateCode($initial, $target); + $this->processInstallPackage($target); + } + + protected function removeCode(PackageInterface $package) + { + $this->processRemovePackage($package); + parent::removeCode($package); + } + + public function processInstallPackage(PackageInterface $package) + { + $installerConfiguration = $this->getInstallerConfiguration($package); + foreach (array_keys($this->handler) as $handler) { + $this->getHandler($handler)->install($installerConfiguration, $package); + } + } + + public function processRemovePackage(PackageInterface $package) + { + $installerConfiguration = $this->getInstallerConfiguration($package); + foreach (array_keys($this->handler) as $handler) { + $this->getHandler($handler)->remove($installerConfiguration, $package); + } + } + + public function getInstallerConfiguration(PackageInterface $package): InstallerConfiguration + { + $directory = $this->getInstallPath($package); + $configurationManifestPath = $this->filesystem->normalizePath($directory . DIRECTORY_SEPARATOR . 'manifest.json'); + $importedConfiguration = []; + if (\file_exists($configurationManifestPath)) { + $importedConfiguration = json_decode(file_get_contents($configurationManifestPath), true, 512, JSON_THROW_ON_ERROR); + } + + return ConfigurationFactory::fromArray($importedConfiguration); + } +} diff --git a/src/Installer/Plugin.php b/src/Installer/Plugin.php new file mode 100644 index 0000000..49ccc33 --- /dev/null +++ b/src/Installer/Plugin.php @@ -0,0 +1,37 @@ +getInstallationManager()->addInstaller($installer); + } + + public function deactivate( + Composer $composer, + IOInterface $io + ) { + } + + public function uninstall( + Composer $composer, + IOInterface $io + ) { + } +} diff --git a/src/Service/GitignoreService.php b/src/Service/GitignoreService.php new file mode 100644 index 0000000..26bc527 --- /dev/null +++ b/src/Service/GitignoreService.php @@ -0,0 +1,110 @@ +ensureGitignoreExists(); + $this->lines = $this->removeDuplicates(file($this->gitignore, FILE_IGNORE_NEW_LINES)); + } + + public function addEntry(string $entry): void + { + $entry = $this->prependSlashIfNotExist($entry); + if (!in_array($entry, $this->lines)) { + $this->lines[] = $entry; + } + $this->hasChanges = true; + } + + public function removeEntry(string $entry): void + { + $entry = $this->prependSlashIfNotExist($entry); + $key = array_search($entry, $this->lines); + if (false !== $key) { + unset($this->lines[$key]); + $this->hasChanges = true; + // renumber array + $this->lines = array_values($this->lines); + } + } + + public function getEntries(): array + { + return $this->lines; + } + + public function write(): void + { + if ($this->hasChanges) { + file_put_contents($this->gitignore, implode("\n", $this->lines) . "\n"); + } + } + + private function prependSlashIfNotExist(string $file): string + { + return sprintf('/%s', ltrim($file, '/')); + } + + private function removeDuplicates(array $lines): array + { + // remove empty lines + $duplicates = array_filter($lines); + // remove comments + $duplicates = array_filter($duplicates, function ($line) { + return strpos($line, '#') !== 0; + }); + // check if duplicates exist + if (count($duplicates) !== count(array_unique($duplicates))) { + $duplicates = array_filter(array_count_values($duplicates), function ($count) { + return $count > 1; + }); + // search from bottom to top + $lines = array_reverse($lines); + foreach ($duplicates as $duplicate => $count) { + // remove all duplicates, except the first one + for ($i = 1; $i < $count; $i++) { + $key = array_search($duplicate, $lines); + unset($lines[$key]); + } + } + // restore original order + $lines = array_values(array_reverse($lines)); + } + return $lines; + } + + private function ensureGitignoreExists() + { + if (!file_exists($this->gitignore)) { + file_put_contents($this->gitignore, "\n"); + } + } +} diff --git a/src/bootstrap.php b/src/bootstrap.php new file mode 100644 index 0000000..acfb4b6 --- /dev/null +++ b/src/bootstrap.php @@ -0,0 +1,21 @@ +createConfigurationInstaller(); + $this->assertTrue($installer->supports('project-configuration')); + } + + public function testConfigurationInstallerSupportConfigurationTypeFromPackage() + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMock('bk2k/configuration-test', 'project-configuration'); + $this->assertTrue($installer->supports($package->getType())); + } + + public function testConfigurationInstallerIsNotSupportedWithOtherPackageType() + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMock('bk2k/configuration-test', 'configuration'); + $this->assertFalse($installer->supports($package->getType())); + } + + /** + * @dataProvider provideFilesForTesting + */ + public function testFilesCreated(array $packageFiles, array $expectedResult) + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMockWithConfigurationFiles($installer, $packageFiles); + $installer->install($this->repository, $package); + + foreach ($expectedResult['files'] as $file) { + $this->assertFileExists($this->rootDirectory . DIRECTORY_SEPARATOR . $file); + } + } + + /** + * @dataProvider provideFilesForTesting + * @depends testFilesCreated + */ + public function testFilesRemoved(array $packageFiles, array $expectedResult) + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMockWithConfigurationFiles($installer, $packageFiles); + $installer->install($this->repository, $package); + + $this->repository + ->expects($this->exactly(1)) + ->method('hasPackage') + ->with($package) + ->willReturn(true) + ; + + $installer->uninstall($this->repository, $package); + + foreach ($expectedResult['files'] as $file) { + $this->assertFileDoesNotExist($this->rootDirectory . DIRECTORY_SEPARATOR . $file); + } + } + + /** + * @dataProvider provideFilesForTesting + */ + public function testGitignoreCreatedIfNotPresent(array $packageFiles, array $expectedResult) + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMockWithConfigurationFiles($installer, $packageFiles); + $this->assertFileDoesNotExist($this->rootDirectory . DIRECTORY_SEPARATOR . '.gitignore'); + + $installer->install($this->repository, $package); + $this->assertFileExists($this->rootDirectory . DIRECTORY_SEPARATOR . '.gitignore'); + } + + /** + * @dataProvider provideFilesForTesting + * @depends testGitignoreCreatedIfNotPresent + */ + public function testGitignoreEntriesUpdated(array $packageFiles, array $expectedResult) + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMockWithConfigurationFiles($installer, $packageFiles); + $installer->install($this->repository, $package); + + $gitignore = new GitignoreService($this->rootDirectory . DIRECTORY_SEPARATOR . '.gitignore'); + $entries = $gitignore->getEntries(); + + foreach ($expectedResult['gitignore'] as $entry) { + $this->assertTrue(in_array($entry, $entries)); + } + } + + /** + * @dataProvider provideFilesForTesting + * @depends testGitignoreCreatedIfNotPresent + * @depends testGitignoreEntriesUpdated + */ + public function testGitignoreEntriesRemoved(array $packageFiles, array $expectedResult) + { + $installer = $this->createConfigurationInstaller(); + $package = $this->createPackageMockWithConfigurationFiles($installer, $packageFiles); + $installer->install($this->repository, $package); + + $this->repository + ->expects($this->exactly(1)) + ->method('hasPackage') + ->with($package) + ->willReturn(true) + ; + + $installer->uninstall($this->repository, $package); + $gitignore = new GitignoreService($this->rootDirectory . DIRECTORY_SEPARATOR . '.gitignore'); + $entries = $gitignore->getEntries(); + + foreach ($expectedResult['gitignore'] as $entry) { + $this->assertFalse(in_array($entry, $entries)); + } + } + + public function provideFilesForTesting() + { + return [ + 'basic usage' => [ + [ + 'README.md' => 'readme content', + 'manifest.json' => \json_encode([ + 'files' => [ + 'README.md' => 'README.md', + ], + 'gitignore' =>[ + '/README.md', + ] + ]) + ], + [ + 'files' => [ + 'README.md' + ], + 'gitignore' => [ + '/README.md' + ] + ] + ], + 'files without target' => [ + [ + 'README.md' => 'readme content', + 'LICENSE' => 'license content', + 'manifest.json' => \json_encode([ + 'files' => [ + 'README.md', + 'LICENSE' + ], + 'gitignore' =>[ + '/README.md', + '/LICENSE', + ] + ]) + ], + [ + 'files' => [ + 'README.md', + 'LICENSE' + ], + 'gitignore' => [ + '/README.md', + '/LICENSE', + ] + ] + ], + 'files in folders' => [ + [ + 'src/file1.md' => 'file1 content', + 'src/file2.md' => 'file2 content', + 'manifest.json' => \json_encode([ + 'files' => [ + 'src/file1.md', + 'src/file2.md' => 'README.md' + ], + 'gitignore' =>[ + '/src/file1.md', + '/README.md', + ] + ]) + ], + [ + 'files' => [ + 'src/file1.md', + 'README.md' + ], + 'gitignore' => [ + '/src/file1.md', + '/README.md', + ] + ] + ], + ]; + } +} diff --git a/tests/Installer/InstallerTestCase.php b/tests/Installer/InstallerTestCase.php new file mode 100644 index 0000000..dd5cadc --- /dev/null +++ b/tests/Installer/InstallerTestCase.php @@ -0,0 +1,173 @@ +previousDirectory = getcwd(); + $this->rootDirectory = TestCase::getUniqueTmpDirectory(); + chdir($this->rootDirectory); + + $this->composer = new Composer(); + $this->composer->setConfig($this->createComposerConfig()); + + /** @var InstallationManager */ + $installationManager = $this->createMock(InstallationManager::class); + $this->composer->setInstallationManager($installationManager); + + /** @var DownloadManager */ + $downloadManager = $this->getMockBuilder(DownloadManager::class)->disableOriginalConstructor()->getMock(); + $this->composer->setDownloadManager($downloadManager); + + /** @var RootPackage|\PHPUnit_Framework_MockObject_MockObject $package */ + $package = $this->createMock(RootPackageInterface::class); + $this->composer->setPackage($package); + + $this->repository = $this->createMock(InstalledRepositoryInterface::class); + $this->io = $this->createMock(IOInterface::class); + } + + protected function tearDown(): void + { + chdir($this->previousDirectory); + if (is_dir($this->rootDirectory)) { + $filesystem = new Filesystem; + $filesystem->removeDirectory($this->rootDirectory); + } + } + + protected function createPackageMock(string $prettyName, $type = 'library', array $extra = []): Package + { + /** @var Package|\PHPUnit_Framework_MockObject_MockObject $package */ + $package = $this->getMockBuilder(Package::class) + ->setConstructorArgs([md5(uniqid()), 'dev-develop', 'dev-develop']) + ->getMock() + ; + $package + ->expects($this->any()) + ->method('getType') + ->willReturn($type) + ; + $package + ->expects($this->any()) + ->method('getPrettyName') + ->willReturn($prettyName) + ; + $package + ->expects($this->any()) + ->method('getPrettyVersion') + ->willReturn('dev-develop') + ; + $package + ->expects($this->any()) + ->method('getVersion') + ->willReturn('dev-develop') + ; + $package + ->expects($this->any()) + ->method('getInstallationSource') + ->willReturn('source') + ; + $package + ->expects($this->any()) + ->method('getExtra') + ->willReturn($extra) + ; + + return $package; + } + + protected function createPackageMockWithConfigurationFiles(InstallerInterface $installer, array $files = []): Package + { + $package = $this->createPackageMock( + 'bk2k/configuration-test', + 'project-configuration' + ); + + $packageInstallationPath = $installer->getInstallPath($package); + $filesystem = new Filesystem; + $filesystem->ensureDirectoryExists($packageInstallationPath); + + if (count($files) > 0) { + foreach ($files as $filename => $fileContent) { + $path = $filesystem->normalizePath($packageInstallationPath . DIRECTORY_SEPARATOR . $filename); + $filesystem->ensureDirectoryExists(dirname($path)); + file_put_contents($path, $fileContent); + } + } + + return $package; + } + + protected function createConfigurationInstaller(): InstallerInterface + { + return new ConfigurationInstaller($this->io, $this->composer); + } + + protected function createComposerConfig(): Config + { + $config = new Config(); + $config->merge([ + 'config' => [ + 'vendor-dir' => $this->rootDirectory . DIRECTORY_SEPARATOR . 'vendor', + 'bin-dir' => $this->rootDirectory . DIRECTORY_SEPARATOR . 'bin', + ], + 'repositories' => ['packagist' => false], + ]); + + return $config; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..ad17b01 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,31 @@ +add('BK2K\ConfigurationInstallerTest\Installer', __DIR__);