Skip to content

Commit 9c3fd5c

Browse files
committed
wip: Refactor DependenciesCommand
1 parent 6cbb7b2 commit 9c3fd5c

2 files changed

Lines changed: 147 additions & 134 deletions

File tree

src/Console/Command/DependenciesCommand.php

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818

1919
namespace FastForward\DevTools\Console\Command;
2020

21+
use Composer\Command\BaseCommand;
22+
use FastForward\DevTools\Process\ProcessBuilderInterface;
23+
use FastForward\DevTools\Process\ProcessQueueInterface;
24+
use Symfony\Component\Config\FileLocatorInterface;
2125
use Symfony\Component\Console\Attribute\AsCommand;
2226
use Symfony\Component\Console\Input\InputInterface;
2327
use Symfony\Component\Console\Output\OutputInterface;
24-
use Symfony\Component\Process\Process;
2528

2629
/**
2730
* Orchestrates dependency analysis across the supported Composer analyzers.
@@ -34,8 +37,16 @@
3437
aliases: ['deps'],
3538
help: 'This command runs composer-dependency-analyser and composer-unused to report missing and unused Composer dependencies.'
3639
)]
37-
final class DependenciesCommand extends AbstractCommand
40+
final class DependenciesCommand extends BaseCommand
3841
{
42+
public function __construct(
43+
private readonly ProcessBuilderInterface $processBuilder,
44+
private readonly ProcessQueueInterface $processQueue,
45+
private readonly FileLocatorInterface $fileLocator,
46+
) {
47+
return parent::__construct();
48+
}
49+
3950
/**
4051
* Executes the dependency analysis workflow.
4152
*
@@ -52,19 +63,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int
5263
{
5364
$output->writeln('<info>Running dependency analysis...</info>');
5465

55-
$composerJson = $this->getConfigFile('composer.json');
66+
$composerJson = $this->fileLocator->locate('composer.json');
67+
68+
$composerUnused = $this->processBuilder
69+
->withArgument($composerJson)
70+
->withArgument('--no-progress')
71+
->build('vendor/bin/composer-unused');
72+
73+
$composerDependencyAnalyser = $this->processBuilder
74+
->withArgument('--composer-json', $composerJson)
75+
->withArgument('--ignore-unused-deps')
76+
->withArgument('--ignore-prod-only-in-dev-deps')
77+
->build('vendor/bin/composer-dependency-analyser');
5678

57-
$results[] = $this->runProcess(
58-
new Process(['vendor/bin/composer-unused', $composerJson, '--no-progress']),
59-
$output
60-
);
61-
$results[] = $this->runProcess(new Process([
62-
'vendor/bin/composer-dependency-analyser',
63-
'--composer-json=' . $composerJson,
64-
'--ignore-unused-deps',
65-
'--ignore-prod-only-in-dev-deps',
66-
]), $output);
79+
$this->processQueue->add($composerUnused);
80+
$this->processQueue->add($composerDependencyAnalyser);
6781

68-
return \in_array(self::FAILURE, $results, true) ? self::FAILURE : self::SUCCESS;
82+
return $this->processQueue->run($output);
6983
}
7084
}

tests/Console/Command/DependenciesCommandTest.php

Lines changed: 119 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -19,163 +19,155 @@
1919
namespace FastForward\DevTools\Tests\Console\Command;
2020

2121
use FastForward\DevTools\Console\Command\DependenciesCommand;
22+
use FastForward\DevTools\Process\ProcessBuilderInterface;
23+
use FastForward\DevTools\Process\ProcessQueueInterface;
2224
use PHPUnit\Framework\Attributes\CoversClass;
2325
use PHPUnit\Framework\Attributes\Test;
26+
use PHPUnit\Framework\TestCase;
2427
use Prophecy\Argument;
28+
use Prophecy\PhpUnit\ProphecyTrait;
29+
use Prophecy\Prophecy\ObjectProphecy;
30+
use ReflectionMethod;
31+
use Symfony\Component\Config\FileLocatorInterface;
32+
use Symfony\Component\Console\Input\InputInterface;
2533
use Symfony\Component\Console\Output\OutputInterface;
2634
use Symfony\Component\Process\Process;
2735

28-
use function Safe\getcwd;
29-
use function str_contains;
30-
3136
#[CoversClass(DependenciesCommand::class)]
32-
final class DependenciesCommandTest extends AbstractCommandTestCase
37+
final class DependenciesCommandTest extends TestCase
3338
{
34-
/**
35-
* @return string
36-
*/
37-
protected function getCommandClass(): string
38-
{
39-
return DependenciesCommand::class;
40-
}
39+
use ProphecyTrait;
4140

42-
/**
43-
* @return string
44-
*/
45-
protected function getCommandName(): string
46-
{
47-
return 'dependencies';
48-
}
41+
private ObjectProphecy $fileLocator;
4942

50-
/**
51-
* @return string
52-
*/
53-
protected function getCommandDescription(): string
54-
{
55-
return 'Analyzes missing and unused Composer dependencies.';
56-
}
43+
private ObjectProphecy $processBuilder;
44+
45+
private ObjectProphecy $processQueue;
46+
47+
private ObjectProphecy $input;
48+
49+
private ObjectProphecy $output;
50+
51+
private ObjectProphecy $processUnused;
52+
53+
private ObjectProphecy $processDepAnalyser;
54+
55+
private DependenciesCommand $command;
5756

5857
/**
59-
* @return string
58+
* @return void
6059
*/
61-
protected function getCommandHelp(): string
60+
protected function setUp(): void
6261
{
63-
return 'This command runs composer-dependency-analyser and composer-unused to report missing and unused Composer dependencies.';
62+
$this->fileLocator = $this->prophesize(FileLocatorInterface::class);
63+
$this->processBuilder = $this->prophesize(ProcessBuilderInterface::class);
64+
$this->processQueue = $this->prophesize(ProcessQueueInterface::class);
65+
$this->input = $this->prophesize(InputInterface::class);
66+
$this->output = $this->prophesize(OutputInterface::class);
67+
$this->processUnused = $this->prophesize(Process::class);
68+
$this->processDepAnalyser = $this->prophesize(Process::class);
69+
70+
$this->processBuilder->withArgument(Argument::any())
71+
->willReturn($this->processBuilder->reveal());
72+
$this->processBuilder->withArgument(Argument::any(), Argument::any())
73+
->willReturn($this->processBuilder->reveal());
74+
75+
$this->processBuilder->build(Argument::any())
76+
->willReturn($this->processUnused->reveal());
77+
78+
$this->command = new DependenciesCommand(
79+
$this->processBuilder->reveal(),
80+
$this->processQueue->reveal(),
81+
$this->fileLocator->reveal(),
82+
);
6483
}
6584

6685
/**
6786
* @return void
6887
*/
69-
protected function setUp(): void
88+
#[Test]
89+
public function commandWillSetExpectedNameDescriptionAndHelp(): void
7090
{
71-
parent::setUp();
72-
73-
$this->output->writeln(Argument::type('string'));
74-
75-
$cwd = getcwd();
76-
$this->filesystem->exists($cwd . '/composer.json')->willReturn(true);
91+
self::assertSame('dependencies', $this->command->getName());
92+
self::assertSame(
93+
'Analyzes missing and unused Composer dependencies.',
94+
$this->command->getDescription()
95+
);
96+
self::assertSame(
97+
'This command runs composer-dependency-analyser and composer-unused to report missing and unused Composer dependencies.',
98+
$this->command->getHelp()
99+
);
77100
}
78101

79102
/**
80103
* @return void
81104
*/
82105
#[Test]
83-
public function executeWillReturnSuccessWhenBothToolsSucceed(): void
106+
public function commandCanBeConstructedWithDependencies(): void
84107
{
85-
$processUnused = $this->prophesize(Process::class);
86-
$processUnused->isSuccessful()
87-
->willReturn(true);
88-
89-
$processDepAnalyser = $this->prophesize(Process::class);
90-
$processDepAnalyser->isSuccessful()
91-
->willReturn(true);
92-
93-
$this->processHelper
94-
->run(Argument::type(OutputInterface::class), Argument::that(
95-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-unused')
96-
), Argument::cetera())
97-
->willReturn($processUnused->reveal())
98-
->shouldBeCalled();
99-
100-
$this->processHelper
101-
->run(Argument::type(OutputInterface::class), Argument::that(
102-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-dependency-analyser')
103-
), Argument::cetera())
104-
->willReturn($processDepAnalyser->reveal())
105-
->shouldBeCalled();
106-
107-
$this->output->writeln('<info>Running dependency analysis...</info>')
108-
->shouldBeCalled();
109-
110-
self::assertSame(DependenciesCommand::SUCCESS, $this->invokeExecute());
108+
self::assertInstanceOf(DependenciesCommand::class, $this->command);
111109
}
112110

113111
/**
114112
* @return void
115113
*/
116114
#[Test]
117-
public function executeWillReturnFailureWhenFirstToolFails(): void
115+
public function executeWillReturnSuccessWhenBothToolsSucceed(): void
118116
{
119-
$processUnused = $this->prophesize(Process::class);
120-
$processUnused->isSuccessful()
121-
->willReturn(false);
117+
$this->fileLocator->locate('composer.json')
118+
->willReturn('/path/to/composer.json');
122119

123-
$processDepAnalyser = $this->prophesize(Process::class);
124-
$processDepAnalyser->isSuccessful()
125-
->willReturn(true);
126-
127-
$this->processHelper
128-
->run(Argument::type(OutputInterface::class), Argument::that(
129-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-unused')
130-
), Argument::cetera())
131-
->willReturn($processUnused->reveal())
120+
$this->processBuilder->build('vendor/bin/composer-unused')
121+
->willReturn($this->processUnused->reveal());
122+
$this->processBuilder->build('vendor/bin/composer-dependency-analyser')
123+
->willReturn($this->processDepAnalyser->reveal());
124+
125+
$this->processQueue->add($this->processUnused->reveal())
126+
->shouldBeCalled();
127+
$this->processQueue->add($this->processDepAnalyser->reveal())
132128
->shouldBeCalled();
133129

134-
$this->processHelper
135-
->run(Argument::type(OutputInterface::class), Argument::that(
136-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-dependency-analyser')
137-
), Argument::cetera())
138-
->willReturn($processDepAnalyser->reveal())
130+
$this->processQueue->run($this->output->reveal())
131+
->willReturn(ProcessQueueInterface::SUCCESS)
139132
->shouldBeCalled();
140133

141134
$this->output->writeln('<info>Running dependency analysis...</info>')
142135
->shouldBeCalled();
143136

144-
self::assertSame(DependenciesCommand::FAILURE, $this->invokeExecute());
137+
$result = $this->executeCommand();
138+
139+
self::assertSame(DependenciesCommand::SUCCESS, $result);
145140
}
146141

147142
/**
148143
* @return void
149144
*/
150145
#[Test]
151-
public function executeWillReturnFailureWhenSecondToolFails(): void
146+
public function executeWillReturnFailureWhenProcessQueueFails(): void
152147
{
153-
$processUnused = $this->prophesize(Process::class);
154-
$processUnused->isSuccessful()
155-
->willReturn(true);
148+
$this->fileLocator->locate('composer.json')
149+
->willReturn('/path/to/composer.json');
156150

157-
$processDepAnalyser = $this->prophesize(Process::class);
158-
$processDepAnalyser->isSuccessful()
159-
->willReturn(false);
160-
161-
$this->processHelper
162-
->run(Argument::type(OutputInterface::class), Argument::that(
163-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-unused')
164-
), Argument::cetera())
165-
->willReturn($processUnused->reveal())
151+
$this->processBuilder->build('vendor/bin/composer-unused')
152+
->willReturn($this->processUnused->reveal());
153+
$this->processBuilder->build('vendor/bin/composer-dependency-analyser')
154+
->willReturn($this->processDepAnalyser->reveal());
155+
156+
$this->processQueue->add($this->processUnused->reveal())
157+
->shouldBeCalled();
158+
$this->processQueue->add($this->processDepAnalyser->reveal())
166159
->shouldBeCalled();
167160

168-
$this->processHelper
169-
->run(Argument::type(OutputInterface::class), Argument::that(
170-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-dependency-analyser')
171-
), Argument::cetera())
172-
->willReturn($processDepAnalyser->reveal())
161+
$this->processQueue->run($this->output->reveal())
162+
->willReturn(ProcessQueueInterface::FAILURE)
173163
->shouldBeCalled();
174164

175165
$this->output->writeln('<info>Running dependency analysis...</info>')
176166
->shouldBeCalled();
177167

178-
self::assertSame(DependenciesCommand::FAILURE, $this->invokeExecute());
168+
$result = $this->executeCommand();
169+
170+
self::assertSame(DependenciesCommand::FAILURE, $result);
179171
}
180172

181173
/**
@@ -184,43 +176,50 @@ public function executeWillReturnFailureWhenSecondToolFails(): void
184176
#[Test]
185177
public function executeWillCallBothDependencyToolsWithComposerJson(): void
186178
{
187-
$cwd = getcwd();
188-
$composerJsonPath = $cwd . '/composer.json';
179+
$composerJsonPath = '/path/to/composer.json';
189180

190-
$this->filesystem->exists($composerJsonPath)
191-
->willReturn(true);
181+
$this->fileLocator->locate('composer.json')
182+
->willReturn($composerJsonPath);
192183

193184
$processUnused = $this->prophesize(Process::class);
194-
$processUnused->isSuccessful()
195-
->willReturn(true);
196185
$processUnused->getCommandLine()
197186
->willReturn('vendor/bin/composer-unused ' . $composerJsonPath . ' --no-progress');
198187

199188
$processDepAnalyser = $this->prophesize(Process::class);
200-
$processDepAnalyser->isSuccessful()
201-
->willReturn(true);
202189
$processDepAnalyser->getCommandLine()
203190
->willReturn(
204191
'vendor/bin/composer-dependency-analyser --composer-json=' . $composerJsonPath . ' --ignore-unused-deps --ignore-prod-only-in-dev-deps'
205192
);
206193

207-
$this->processHelper
208-
->run(Argument::type(OutputInterface::class), Argument::that(
209-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-unused')
210-
), Argument::cetera())
211-
->willReturn($processUnused->reveal())
194+
$this->processBuilder->build('vendor/bin/composer-unused')
195+
->willReturn($processUnused->reveal());
196+
$this->processBuilder->build('vendor/bin/composer-dependency-analyser')
197+
->willReturn($processDepAnalyser->reveal());
198+
199+
$this->processQueue->add($processUnused->reveal())
200+
->shouldBeCalled();
201+
$this->processQueue->add($processDepAnalyser->reveal())
212202
->shouldBeCalled();
213203

214-
$this->processHelper
215-
->run(Argument::type(OutputInterface::class), Argument::that(
216-
static fn(Process $p): bool => str_contains($p->getCommandLine(), 'composer-dependency-analyser')
217-
), Argument::cetera())
218-
->willReturn($processDepAnalyser->reveal())
204+
$this->processQueue->run($this->output->reveal())
205+
->willReturn(ProcessQueueInterface::SUCCESS)
219206
->shouldBeCalled();
220207

221208
$this->output->writeln('<info>Running dependency analysis...</info>')
222209
->shouldBeCalled();
223210

224-
self::assertSame(DependenciesCommand::SUCCESS, $this->invokeExecute());
211+
$result = $this->executeCommand();
212+
213+
self::assertSame(DependenciesCommand::SUCCESS, $result);
214+
}
215+
216+
/**
217+
* @return int
218+
*/
219+
private function executeCommand(): int
220+
{
221+
$reflectionMethod = new ReflectionMethod($this->command, 'execute');
222+
223+
return $reflectionMethod->invoke($this->command, $this->input->reveal(), $this->output->reveal());
225224
}
226-
}
225+
}

0 commit comments

Comments
 (0)