packages/requirements/tests/Unit/Console/ApplicationTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Console;
6
7use JsonException;
8use Override;
9use PHPUnit\Framework\Attributes\CoversClass;
10use PHPUnit\Framework\Attributes\DataProvider;
11use PHPUnit\Framework\Attributes\Small;
12use PHPUnit\Framework\Attributes\UsesClass;
13use PHPUnit\Framework\TestCase;
14use Requirements\Config\CoverageThresholds;
15use Requirements\Config\DefinitionReader;
16use Requirements\Config\Definitions;
17use Requirements\Config\DocumentReader;
18use Requirements\Config\ExtensionClasses;
19use Requirements\Config\JsonSchemaFile;
20use Requirements\Config\LinkValidator;
21use Requirements\Config\Loader;
22use Requirements\Config\MarkdownDocument;
23use Requirements\Config\SchemaValidator;
24use Requirements\Console\Application;
25use Requirements\Console\CommandHandler;
26use Requirements\Console\CommandLine;
27use Requirements\Console\CoverageTable;
28use Requirements\Console\Executor;
29use Requirements\Console\ItemRecord;
30use Requirements\Console\Options;
31use Requirements\Console\Overview;
32use Requirements\Console\Reporter;
33use Requirements\Console\SpecificationReport;
34use Requirements\Console\SpecificationTable;
35use Requirements\Console\Text;
36use Requirements\Console\Verdict;
37use Requirements\Ears\ConditionOrder;
38use Requirements\Ears\LiteralMask;
39use Requirements\Ears\SystemResponse;
40use Requirements\Ears\Validator;
41use Requirements\Ears\Wording;
42use Requirements\Input\Fields;
43use Requirements\Model\Excerpt;
44use Requirements\Model\Item;
45use Requirements\Model\ItemValidator;
46use Requirements\Model\Project;
47use Requirements\Model\Source;
48use Requirements\Report\Analysis;
49use Requirements\Report\Analyzer;
50use Requirements\Report\Claims;
51use Requirements\Report\Coverage;
52use Requirements\Report\EvidenceMatcher;
53use Requirements\Report\SourceUnit;
54use Requirements\Report\UnitCollector;
55use Requirements\Source\DomSource;
56use Requirements\Source\JsonSource;
57use Requirements\Source\LocalFile;
58use Requirements\Source\Registry as SourceRegistry;
59use Requirements\Source\ResourceLoader;
60use Requirements\Source\ResourceLocation;
61use Requirements\Source\TextFragment;
62use Requirements\Source\TextSource;
63use Requirements\Source\Unit;
64use Requirements\Test\Registry as TestRegistry;
65use Requirements\Verification\TargetResults;
66use Requirements\Verification\TestExecution;
67use Requirements\Verification\VerificationResult;
68use Requirements\Verification\Verifier;
69use Tests\Fake\ProjectDirectory;
70
71#[CoversClass(Application::class)]
72#[UsesClass(CoverageThresholds::class)]
73#[UsesClass(DefinitionReader::class)]
74#[UsesClass(Definitions::class)]
75#[UsesClass(DocumentReader::class)]
76#[UsesClass(ExtensionClasses::class)]
77#[UsesClass(JsonSchemaFile::class)]
78#[UsesClass(LinkValidator::class)]
79#[UsesClass(Loader::class)]
80#[UsesClass(MarkdownDocument::class)]
81#[UsesClass(SchemaValidator::class)]
82#[UsesClass(CommandHandler::class)]
83#[UsesClass(CommandLine::class)]
84#[UsesClass(Executor::class)]
85#[UsesClass(SpecificationReport::class)]
86#[UsesClass(Options::class)]
87#[UsesClass(Overview::class)]
88#[UsesClass(Reporter::class)]
89#[UsesClass(Text::class)]
90#[UsesClass(Verdict::class)]
91#[UsesClass(ConditionOrder::class)]
92#[UsesClass(LiteralMask::class)]
93#[UsesClass(SystemResponse::class)]
94#[UsesClass(Validator::class)]
95#[UsesClass(Wording::class)]
96#[UsesClass(Fields::class)]
97#[UsesClass(Excerpt::class)]
98#[UsesClass(Item::class)]
99#[UsesClass(ItemValidator::class)]
100#[UsesClass(Project::class)]
101#[UsesClass(Source::class)]
102#[UsesClass(Analysis::class)]
103#[UsesClass(Analyzer::class)]
104#[UsesClass(Claims::class)]
105#[UsesClass(EvidenceMatcher::class)]
106#[UsesClass(SourceUnit::class)]
107#[UsesClass(UnitCollector::class)]
108#[UsesClass(DomSource::class)]
109#[UsesClass(JsonSource::class)]
110#[UsesClass(LocalFile::class)]
111#[UsesClass(SourceRegistry::class)]
112#[UsesClass(ResourceLoader::class)]
113#[UsesClass(ResourceLocation::class)]
114#[UsesClass(TextFragment::class)]
115#[UsesClass(TextSource::class)]
116#[UsesClass(Unit::class)]
117#[UsesClass(TestRegistry::class)]
118#[UsesClass(CoverageTable::class)]
119#[UsesClass(Coverage::class)]
120#[UsesClass(ItemRecord::class)]
121#[UsesClass(SpecificationTable::class)]
122#[UsesClass(VerificationResult::class)]
123#[UsesClass(Verifier::class)]
124#[UsesClass(TestExecution::class)]
125#[UsesClass(TargetResults::class)]
126#[Small]
127final class ApplicationTest extends TestCase
128{
129 #[Override]
130 protected function tearDown(): void
131 {
132 putenv('SHELL_VERBOSITY');
133 unset($_ENV['SHELL_VERBOSITY'], $_SERVER['SHELL_VERBOSITY']);
134 }
135
136 /**
137 * @param list<string> $arguments
138 * @throws JsonException
139 */
140 #[DataProvider('providerRunWithoutAProject')]
141 public function testRunReturnsTheExitCodeWithoutAProject(array $arguments, int $expected): void
142 {
143 self::assertSame($expected, (new Application())->run($arguments));
144 }
145
146 /**
147 * @return array<string, array{list<string>, int}>
148 */
149 public static function providerRunWithoutAProject(): array
150 {
151 return [
152 'overview' => [['--quiet'], 0],
153 'overview with short quiet' => [['-q'], 0],
154 'version' => [['--version', '--quiet'], 0],
155 'unknown command' => [['unknown', '--quiet'], 2],
156 'unknown command as JSON' => [['--json', 'unknown', '--quiet'], 2],
157 'unknown option' => [['lint', '--unknown', '--quiet'], 2],
158 'missing configuration' => [['lint', '--config', '/nonexistent/requirements.yaml', '--quiet'], 2],
159 'missing configuration as JSON' => [['lint', '--config', '/nonexistent/requirements.yaml', '--json', '--quiet'], 2],
160 ];
161 }
162
163 /**
164 * @param list<string> $arguments
165 * @throws JsonException
166 */
167 #[DataProvider('providerRunInAProject')]
168 public function testRunReturnsTheExitCodeOfTheCommand(array $arguments, int $expected): void
169 {
170 $project = new ProjectDirectory();
171 self::assertSame($expected, (new Application())->run([...$arguments, '--config', $project->path('requirements.yaml'), '--quiet']));
172 }
173
174 /**
175 * @return array<string, array{list<string>, int}>
176 */
177 public static function providerRunInAProject(): array
178 {
179 return [
180 'lint' => [['lint'], 0],
181 'lint as JSON' => [['lint', '--json'], 0],
182 'check' => [['check'], 0],
183 'failed coverage gate' => [['coverage', '--min-coverage=100'], 1],
184 'invalid percentage' => [['coverage', '--min-coverage=not-a-number'], 2],
185 'unverified spec' => [['spec'], 0],
186 'strict unverified spec' => [['spec', '--strict'], 1],
187 ];
188 }
189}
190