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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"php": "^8.1",
"ext-json": "*",
"composer-runtime-api": "^2.0",
"composer/composer": "^2.4",
"jawira/case-converter": "^3.5",
"opis/json-schema": "^2.3",
"symfony/console": "^6.0 || ^7.0",
Expand Down
36 changes: 12 additions & 24 deletions src/ConventionalCommits/Configuration/FinderTool.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@

namespace Ramsey\ConventionalCommits\Configuration;

use Composer\Composer;
use Composer\Factory;
use Composer\IO\ConsoleIO;
use JsonException;
use Opis\JsonSchema\Errors\ErrorFormatter;
use Opis\JsonSchema\Errors\ValidationError;
Expand All @@ -32,7 +29,7 @@
use Ramsey\ConventionalCommits\Exception\ComposerNotFound;
use Ramsey\ConventionalCommits\Exception\InvalidArgument;
use Ramsey\ConventionalCommits\Exception\InvalidValue;
use Symfony\Component\Console\Helper\HelperSet;
use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
Expand All @@ -43,6 +40,7 @@
use function gettype;
use function implode;
use function is_array;
use function is_readable;
use function json_decode;
use function realpath;
use function sprintf;
Expand Down Expand Up @@ -84,23 +82,6 @@ public function findConfiguration(
return new DefaultConfiguration($this->loadConfigFromComposer($input, $output));
}

/**
* Returns the Composer instance for the current project
*/
public function getComposer(
InputInterface $input,
OutputInterface $output,
Filesystem $filesystem,
): Composer {
$composerJson = $this->findComposerJson($filesystem);

$composerIO = new ConsoleIO($input, $output, new HelperSet());
$composerFactory = new Factory();

/** @var Composer */
return $composerFactory->createComposer($composerIO, $composerJson, true, null, true);
}

/**
* @return array{typeCase?: string | null, types?: string[], scopeRequired?: bool, scopeCase?: string | null, scopes?: string[], descriptionCase?: string | null, descriptionEndMark?: string | null, bodyRequired?: bool, bodyWrapWidth?: int | null, requiredFooters?: string[]}
*
Expand Down Expand Up @@ -137,10 +118,17 @@ private function loadConfigFromFile(string $file): array
*/
private function loadConfigFromComposer(InputInterface $input, OutputInterface $output): array
{
$composer = $this->getComposer($input, $output, new Filesystem());

$composerJsonPath = $this->findComposerJson(new Filesystem());
if (!is_readable($composerJsonPath)) {
throw new RuntimeException(sprintf('The file "%s" is not readable.', $composerJsonPath));
}
$contents = @file_get_contents($composerJsonPath);
if ($contents === false) {
throw new RuntimeException(sprintf('Could not read file "%s"', $composerJsonPath));
}
$composerData = json_decode($contents, true, flags: JSON_THROW_ON_ERROR);
/** @var array{"ramsey/conventional-commits"?: array{config?: scalar | array{typeCase?: string | null, types?: string[], scopeRequired?: bool, scopeCase?: string | null, scopes?: string[], descriptionCase?: string | null, descriptionEndMark?: string | null, bodyRequired?: bool, bodyWrapWidth?: int | null, requiredFooters?: string[]}, configFile?: scalar}} | null $extra */
$extra = $composer->getPackage()->getExtra();
$extra = is_array($composerData) ? ($composerData['extra'] ?? null) : null;

$config = $extra['ramsey/conventional-commits']['config'] ?? null;
$configFile = $extra['ramsey/conventional-commits']['configFile'] ?? null;
Expand Down
153 changes: 65 additions & 88 deletions tests/ConventionalCommits/Configuration/FinderToolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,25 @@

namespace Ramsey\Test\ConventionalCommits\Configuration;

use Composer\Composer;
use JsonException;
use Mockery\MockInterface;
use Ramsey\ConventionalCommits\Configuration\Configuration;
use Ramsey\ConventionalCommits\Configuration\FinderTool;
use Ramsey\ConventionalCommits\Exception\ComposerNotFound;
use Ramsey\ConventionalCommits\Exception\InvalidArgument;
use Ramsey\ConventionalCommits\Exception\InvalidValue;
use Ramsey\Test\SnapshotsTool;
use Ramsey\Test\TestCase;
use Ramsey\Test\WindowsSafeTextDriver;
use RuntimeException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;

use function file_put_contents;
use function json_encode;
use function realpath;
use function sys_get_temp_dir;
use function unlink;

use const DIRECTORY_SEPARATOR;

Expand All @@ -34,6 +36,8 @@ class FinderToolTest extends TestCase

private OutputInterface & MockInterface $output;

private Filesystem $fileSystem;

protected function setUp(): void
{
parent::setUp();
Expand All @@ -43,6 +47,7 @@ protected function setUp(): void
$this->finderTool = new class () {
use FinderTool;
};
$this->fileSystem = new Filesystem();
}

/**
Expand Down Expand Up @@ -168,79 +173,52 @@ public function testFindConfigurationThrowsExceptionWhenConfigIsInvalid(): void
]);
}

public function testGetComposerFindsComposerJsonForCurrentProject(): void
{
$this->output->shouldReceive('isDebug');
$this->output->allows()->getVerbosity()->andReturn(OutputInterface::VERBOSITY_QUIET);
$this->output->allows()->isDebug()->andReturnFalse();

$filesystem = new Filesystem();

// @phpstan-ignore-next-line
$composer = $this->finderTool->getComposer($this->input, $this->output, $filesystem);

$this->assertInstanceOf(Composer::class, $composer);
$this->assertSame('ramsey/conventional-commits', $composer->getPackage()->getName());
}

public function testGetComposerThrowsExceptionWhenAutoloaderDoesNotExist(): void
public function testFindConfigurationThrowsExceptionWhenComposerJsonDoesNotExist(): void
{
$this->output->allows()->getVerbosity()->andReturn(OutputInterface::VERBOSITY_QUIET);

/** @var Filesystem & MockInterface $filesystem */
$filesystem = $this->mockery(Filesystem::class);
$filesystem->shouldReceive('exists')->twice()->andReturnFalse();
$composerJsonPath = 'path/to/nonexistent/composer.json';

$this->expectException(ComposerNotFound::class);
$this->expectExceptionMessage(
'Could not find the autoloader. Did you run composer install or composer update?',
);
$finderTool = new class () {
use FinderTool;

// @phpstan-ignore-next-line
$this->finderTool->getComposer($this->input, $this->output, $filesystem);
}
public string $composerJsonPath;

public function testGetComposerThrowsExceptionWhenComposerJsonDoesNotExist(): void
{
$this->output->allows()->getVerbosity()->andReturn(OutputInterface::VERBOSITY_QUIET);
public function findComposerJson(Filesystem $filesystem): string
{
return $this->composerJsonPath;
}
};

/** @var Filesystem & MockInterface $filesystem */
$filesystem = $this->mockery(Filesystem::class);
$filesystem->shouldReceive('exists')->andReturn(false, true, false);
$finderTool->composerJsonPath = $composerJsonPath;

$this->expectException(ComposerNotFound::class);
$this->expectExceptionMessage('Could not find composer.json.');
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage("The file \"{$composerJsonPath}\" is not readable.");

// @phpstan-ignore-next-line
$this->finderTool->getComposer($this->input, $this->output, $filesystem);
$finderTool->findConfiguration($this->input, $this->output);
}

public function testFindConfigurationThrowsExceptionWhenComposerHasInvalidValue(): void
{
/** @var Composer & MockInterface $composer */
$composer = $this->mockery(Composer::class, [
'getPackage->getExtra' => [
$composerJsonPath = $this->fileSystem->tempnam(sys_get_temp_dir(), 'cc_', '.json');
file_put_contents($composerJsonPath, json_encode([
'extra' => [
'ramsey/conventional-commits' => [
'config' => 'invalid value',
],
],
]);
]));

$finderTool = new class () {
use FinderTool;

public Composer $composer;
public string $composerJsonPath;

public function getComposer(
InputInterface $input,
OutputInterface $output,
Filesystem $filesystem,
): Composer {
return $this->composer;
public function findComposerJson(Filesystem $filesystem): string
{
return $this->composerJsonPath;
}
};

$finderTool->composer = $composer;
$finderTool->composerJsonPath = $composerJsonPath;

$this->expectException(InvalidValue::class);
$this->expectExceptionMessage(
Expand All @@ -249,103 +227,102 @@ public function getComposer(
);

$finderTool->findConfiguration($this->input, $this->output);

@unlink($composerJsonPath);
}

public function testFindConfigurationReturnsConfigurationUsingComposerConfig(): void
{
/** @var Composer & MockInterface $composer */
$composer = $this->mockery(Composer::class, [
'getPackage->getExtra' => [
$composerJsonPath = $this->fileSystem->tempnam(sys_get_temp_dir(), 'cc_', '.json');
file_put_contents($composerJsonPath, json_encode([
'extra' => [
'ramsey/conventional-commits' => [
'configFile' => (string) realpath(__DIR__ . '/../../configs/config-03.json'),
'config' => [
'typeCase' => 'pascal',
],
],
],
]);
]));

$finderTool = new class () {
use FinderTool;

public Composer $composer;
public string $composerJsonPath;

public function getComposer(
InputInterface $input,
OutputInterface $output,
Filesystem $filesystem,
): Composer {
return $this->composer;
public function findComposerJson(Filesystem $filesystem): string
{
return $this->composerJsonPath;
}
};

$finderTool->composer = $composer;
$finderTool->composerJsonPath = $composerJsonPath;

/** @var Configuration $configuration */
$configuration = $finderTool->findConfiguration($this->input, $this->output);

$this->assertMatchesSnapshot(json_encode($configuration), new WindowsSafeTextDriver());

@unlink($composerJsonPath);
}

public function testFindConfigurationReturnsConfigurationUsingComposerConfigFile(): void
{
/** @var Composer & MockInterface $composer */
$composer = $this->mockery(Composer::class, [
'getPackage->getExtra' => [
$composerJsonPath = $this->fileSystem->tempnam(sys_get_temp_dir(), 'cc_', '.json');
file_put_contents($composerJsonPath, json_encode([
'extra' => [
'ramsey/conventional-commits' => [
'configFile' => (string) realpath(__DIR__ . '/../../configs/config-03.json'),
],
],
]);
]));

$finderTool = new class () {
use FinderTool;

public Composer $composer;
public string $composerJsonPath;

public function getComposer(
InputInterface $input,
OutputInterface $output,
Filesystem $filesystem,
): Composer {
return $this->composer;
public function findComposerJson(Filesystem $filesystem): string
{
return $this->composerJsonPath;
}
};

$finderTool->composer = $composer;
$finderTool->composerJsonPath = $composerJsonPath;

/** @var Configuration $configuration */
$configuration = $finderTool->findConfiguration($this->input, $this->output);

$this->assertMatchesSnapshot(json_encode($configuration), new WindowsSafeTextDriver());

@unlink($composerJsonPath);
}

public function testFindConfigurationReturnsDefaultConfigurationWhenComposerHasNone(): void
{
/** @var Composer & MockInterface $composer */
$composer = $this->mockery(Composer::class, [
'getPackage->getExtra' => [],
]);
$composerJsonPath = $this->fileSystem->tempnam(sys_get_temp_dir(), 'cc_', '.json');
file_put_contents($composerJsonPath, json_encode([
'extra' => [],
]));

$finderTool = new class () {
use FinderTool;

public Composer $composer;
public string $composerJsonPath;

public function getComposer(
InputInterface $input,
OutputInterface $output,
Filesystem $filesystem,
): Composer {
return $this->composer;
public function findComposerJson(Filesystem $filesystem): string
{
return $this->composerJsonPath;
}
};

$finderTool->composer = $composer;
$finderTool->composerJsonPath = $composerJsonPath;

/** @var Configuration $configuration */
$configuration = $finderTool->findConfiguration($this->input, $this->output);

$this->assertMatchesSnapshot(json_encode($configuration), new WindowsSafeTextDriver());

@unlink($composerJsonPath);
}
}