Skip to content

Commit f0233d3

Browse files
theimbenderalcohol
authored andcommitted
Use TestDox for more verbose PHPUnit reporting, fix minor bug
1 parent 6982905 commit f0233d3

File tree

3 files changed

+270
-12
lines changed

3 files changed

+270
-12
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
displayDetailsOnPhpunitDeprecations="true"
66
displayDetailsOnTestsThatTriggerDeprecations="true"
77
displayDetailsOnTestsThatTriggerErrors="true"
8+
testdox="true"
89
>
910
<coverage>
1011
<report>

src/Console/Command/AddCommand.php

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -116,19 +116,23 @@ protected function execute(InputInterface $input, OutputInterface $output): int
116116

117117
protected function isRepositoryValid(string $repositoryUrl, string $type): bool
118118
{
119-
$io = new NullIO();
120-
$config = Factory::createConfig();
121-
$io->loadConfiguration($config);
122-
$downloader = new HttpDownloader($io, $config);
123-
$repository = new VcsRepository(['url' => $repositoryUrl, 'type' => $type], $io, $config, $downloader);
124-
125-
$driver = $repository->getDriver();
126-
if (is_null($driver)) {
127-
return false;
128-
}
119+
try {
120+
$io = new NullIO();
121+
$config = Factory::createConfig();
122+
$io->loadConfiguration($config);
123+
$downloader = new HttpDownloader($io, $config);
124+
$repository = new VcsRepository(['url' => $repositoryUrl, 'type' => $type], $io, $config, $downloader);
125+
126+
$driver = $repository->getDriver();
127+
if (is_null($driver)) {
128+
return false;
129+
}
129130

130-
$information = $driver->getComposerInformation($driver->getRootIdentifier());
131+
$information = $driver->getComposerInformation($driver->getRootIdentifier());
131132

132-
return isset($information['name']) && is_string($information['name']);
133+
return isset($information['name']) && is_string($information['name']);
134+
} catch (\Exception) {
135+
return false;
136+
}
133137
}
134138
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of composer/satis.
7+
*
8+
* (c) Composer <https://github.com/composer>
9+
*
10+
* For the full copyright and license information, please view
11+
* the LICENSE file that was distributed with this source code.
12+
*/
13+
14+
namespace Composer\Satis\Console\Command;
15+
16+
use Composer\Console\Application;
17+
use org\bovigo\vfs\vfsStream;
18+
use PHPUnit\Framework\Attributes\TestDox;
19+
use PHPUnit\Framework\TestCase;
20+
use Symfony\Component\Console\Tester\CommandTester;
21+
22+
#[TestDox('AddCommand')]
23+
class AddCommandTest extends TestCase
24+
{
25+
private string $configPath;
26+
27+
protected function setUp(): void
28+
{
29+
vfsStream::setup('satis');
30+
$this->configPath = vfsStream::url('satis/satis.json');
31+
}
32+
33+
private function createTester(bool $repoValid = true): CommandTester
34+
{
35+
$command = $this->getMockBuilder(AddCommand::class)
36+
->onlyMethods(['isRepositoryValid'])
37+
->getMock();
38+
$command->method('isRepositoryValid')->willReturn($repoValid);
39+
40+
$app = new Application();
41+
$app->addCommand($command);
42+
43+
return new CommandTester($app->find('add'));
44+
}
45+
46+
/** @param array<string, mixed> $config */
47+
private function writeConfig(array $config): void
48+
{
49+
file_put_contents(
50+
$this->configPath,
51+
json_encode($config, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
52+
);
53+
}
54+
55+
/** @return array<string, mixed> */
56+
private function readConfig(): array
57+
{
58+
$contents = file_get_contents($this->configPath);
59+
self::assertIsString($contents);
60+
61+
return json_decode($contents, true);
62+
}
63+
64+
#[TestDox('Rejects an HTTP URL as the config file path')]
65+
public function testRejectsHttpConfigFile(): void
66+
{
67+
$tester = $this->createTester();
68+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => 'http://example.com/satis.json']);
69+
70+
self::assertSame(2, $tester->getStatusCode());
71+
self::assertStringContainsString('Unable to write to remote file', $tester->getDisplay());
72+
}
73+
74+
#[TestDox('Rejects an HTTPS URL as the config file path')]
75+
public function testRejectsHttpsConfigFile(): void
76+
{
77+
$tester = $this->createTester();
78+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => 'https://example.com/satis.json']);
79+
80+
self::assertSame(2, $tester->getStatusCode());
81+
self::assertStringContainsString('Unable to write to remote file', $tester->getDisplay());
82+
}
83+
84+
#[TestDox('Rejects a config file that does not exist')]
85+
public function testRejectsNonExistentFile(): void
86+
{
87+
$tester = $this->createTester();
88+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => vfsStream::url('satis/nonexistent.json')]);
89+
90+
self::assertSame(1, $tester->getStatusCode());
91+
self::assertStringContainsString('File not found', $tester->getDisplay());
92+
}
93+
94+
#[TestDox('Rejects an invalid repository URL')]
95+
public function testRejectsInvalidRepository(): void
96+
{
97+
$this->writeConfig(['name' => 'test/repo', 'repositories' => []]);
98+
$tester = $this->createTester(repoValid: false);
99+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
100+
101+
self::assertSame(3, $tester->getStatusCode());
102+
self::assertStringContainsString('Invalid Repository URL', $tester->getDisplay());
103+
}
104+
105+
#[TestDox('Rejects a repository URL that already exists in the config')]
106+
public function testRejectsDuplicateUrl(): void
107+
{
108+
$this->writeConfig([
109+
'name' => 'test/repo',
110+
'repositories' => [
111+
['type' => 'vcs', 'url' => 'https://github.com/foo/bar.git'],
112+
],
113+
]);
114+
$tester = $this->createTester();
115+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
116+
117+
self::assertSame(4, $tester->getStatusCode());
118+
self::assertStringContainsString('Repository url already added to the file', $tester->getDisplay());
119+
}
120+
121+
#[TestDox('Rejects a repository name that already exists in the config')]
122+
public function testRejectsDuplicateName(): void
123+
{
124+
$this->writeConfig([
125+
'name' => 'test/repo',
126+
'repositories' => [
127+
['type' => 'vcs', 'url' => 'https://github.com/existing/repo.git', 'name' => 'my/package'],
128+
],
129+
]);
130+
$tester = $this->createTester();
131+
$tester->execute([
132+
'url' => 'https://github.com/foo/bar.git',
133+
'file' => $this->configPath,
134+
'--name' => 'my/package',
135+
]);
136+
137+
self::assertSame(5, $tester->getStatusCode());
138+
self::assertStringContainsString('Repository name already added to the file', $tester->getDisplay());
139+
}
140+
141+
#[TestDox('Adds a repository to an empty config')]
142+
public function testAddRepositoryToEmptyConfig(): void
143+
{
144+
$this->writeConfig(['name' => 'test/repo', 'repositories' => []]);
145+
$tester = $this->createTester();
146+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
147+
148+
self::assertSame(0, $tester->getStatusCode());
149+
self::assertStringContainsString('successfully updated', $tester->getDisplay());
150+
151+
$config = $this->readConfig();
152+
self::assertCount(1, $config['repositories']);
153+
self::assertSame('vcs', $config['repositories'][0]['type']);
154+
self::assertSame('https://github.com/foo/bar.git', $config['repositories'][0]['url']);
155+
}
156+
157+
#[TestDox('Includes the name field when --name is provided')]
158+
public function testAddRepositoryWithName(): void
159+
{
160+
$this->writeConfig(['name' => 'test/repo', 'repositories' => []]);
161+
$tester = $this->createTester();
162+
$tester->execute([
163+
'url' => 'https://github.com/foo/bar.git',
164+
'file' => $this->configPath,
165+
'--name' => 'foo/bar',
166+
]);
167+
168+
self::assertSame(0, $tester->getStatusCode());
169+
170+
$config = $this->readConfig();
171+
self::assertSame('foo/bar', $config['repositories'][0]['name']);
172+
}
173+
174+
#[TestDox('Omits the name field when --name is not provided')]
175+
public function testAddRepositoryWithoutName(): void
176+
{
177+
$this->writeConfig(['name' => 'test/repo', 'repositories' => []]);
178+
$tester = $this->createTester();
179+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
180+
181+
self::assertSame(0, $tester->getStatusCode());
182+
183+
$config = $this->readConfig();
184+
self::assertArrayNotHasKey('name', $config['repositories'][0]);
185+
}
186+
187+
#[TestDox('Preserves existing repositories when adding a new one')]
188+
public function testAddRepositoryPreservesExistingRepos(): void
189+
{
190+
$this->writeConfig([
191+
'name' => 'test/repo',
192+
'repositories' => [
193+
['type' => 'vcs', 'url' => 'https://github.com/existing/repo.git'],
194+
],
195+
]);
196+
$tester = $this->createTester();
197+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
198+
199+
self::assertSame(0, $tester->getStatusCode());
200+
201+
$config = $this->readConfig();
202+
self::assertCount(2, $config['repositories']);
203+
self::assertSame('https://github.com/existing/repo.git', $config['repositories'][0]['url']);
204+
self::assertSame('https://github.com/foo/bar.git', $config['repositories'][1]['url']);
205+
}
206+
207+
#[TestDox('Initializes the repositories key when it is missing')]
208+
public function testAddRepositoryInitializesRepositoriesKey(): void
209+
{
210+
$this->writeConfig(['name' => 'test/repo']);
211+
$tester = $this->createTester();
212+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
213+
214+
self::assertSame(0, $tester->getStatusCode());
215+
216+
$config = $this->readConfig();
217+
self::assertCount(1, $config['repositories']);
218+
self::assertSame('https://github.com/foo/bar.git', $config['repositories'][0]['url']);
219+
}
220+
221+
#[TestDox('Replaces a non-array repositories value with a valid array')]
222+
public function testAddRepositoryInitializesNonArrayRepositories(): void
223+
{
224+
$this->writeConfig(['name' => 'test/repo', 'repositories' => 'invalid']);
225+
$tester = $this->createTester();
226+
$tester->execute(['url' => 'https://github.com/foo/bar.git', 'file' => $this->configPath]);
227+
228+
self::assertSame(0, $tester->getStatusCode());
229+
230+
$config = $this->readConfig();
231+
self::assertIsArray($config['repositories']);
232+
self::assertCount(1, $config['repositories']);
233+
self::assertSame('https://github.com/foo/bar.git', $config['repositories'][0]['url']);
234+
}
235+
236+
#[TestDox('isRepositoryValid returns false when the URL causes an exception')]
237+
public function testIsRepositoryValidReturnsFalseOnException(): void
238+
{
239+
$command = new AddCommand();
240+
$method = new \ReflectionMethod($command, 'isRepositoryValid');
241+
242+
self::assertFalse($method->invoke($command, 'not-a-valid-url-at-all', 'vcs'));
243+
}
244+
245+
#[TestDox('isRepositoryValid returns false when no VCS driver matches')]
246+
public function testIsRepositoryValidReturnsFalseOnNullDriver(): void
247+
{
248+
$command = new AddCommand();
249+
$method = new \ReflectionMethod($command, 'isRepositoryValid');
250+
251+
self::assertFalse($method->invoke($command, 'https://example.com/nonexistent-repo.git', 'vcs'));
252+
}
253+
}

0 commit comments

Comments
 (0)