Skip to content

Commit 11ee144

Browse files
authored
Merge pull request #30 from studio-design/feat/console-output-mode
New Feature: console_output パラメータでコンソール出力モードを制御
2 parents 9b36db5 + 9ae5215 commit 11ee144

6 files changed

Lines changed: 651 additions & 43 deletions

File tree

README.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Add the coverage extension to your `phpunit.xml`:
6262
| `strip_prefixes` | No | `[]` | Comma-separated prefixes to strip from request paths (e.g., `/api`) |
6363
| `specs` | No | `front` | Comma-separated spec names for coverage tracking |
6464
| `output_file` | No || File path to write Markdown coverage report (relative paths resolve from `getcwd()`) |
65+
| `console_output` | No | `default` | Console output mode: `default`, `all`, or `uncovered_only` (overridden by `OPENAPI_CONSOLE_OUTPUT` env var) |
6566

6667
*Not required if you call `OpenApiSpecLoader::configure()` manually.
6768

@@ -163,7 +164,11 @@ For Laravel, set the `max_errors` key in `config/openapi-contract-testing.php`.
163164

164165
## Coverage Report
165166

166-
After running tests, the PHPUnit extension prints a coverage report:
167+
After running tests, the PHPUnit extension prints a coverage report. The output format is controlled by the `console_output` parameter (or `OPENAPI_CONSOLE_OUTPUT` environment variable).
168+
169+
### `default` mode (default)
170+
171+
Shows covered endpoints individually and uncovered as a count:
167172

168173
```
169174
OpenAPI Contract Test Coverage
@@ -179,6 +184,56 @@ Covered:
179184
Uncovered: 41 endpoints
180185
```
181186

187+
### `all` mode
188+
189+
Shows both covered and uncovered endpoints individually:
190+
191+
```
192+
OpenAPI Contract Test Coverage
193+
==================================================
194+
195+
[front] 12/45 endpoints (26.7%)
196+
--------------------------------------------------
197+
Covered:
198+
✓ GET /v1/pets
199+
✓ POST /v1/pets
200+
✓ GET /v1/pets/{petId}
201+
✓ DELETE /v1/pets/{petId}
202+
Uncovered:
203+
✗ PUT /v1/pets/{petId}
204+
✗ GET /v1/owners
205+
...
206+
```
207+
208+
### `uncovered_only` mode
209+
210+
Shows uncovered endpoints individually and covered as a count — useful for large APIs where you want to focus on missing coverage:
211+
212+
```
213+
OpenAPI Contract Test Coverage
214+
==================================================
215+
216+
[front] 12/45 endpoints (26.7%)
217+
--------------------------------------------------
218+
Covered: 12 endpoints
219+
Uncovered:
220+
✗ PUT /v1/pets/{petId}
221+
✗ GET /v1/owners
222+
...
223+
```
224+
225+
You can set the mode via `phpunit.xml`:
226+
227+
```xml
228+
<parameter name="console_output" value="uncovered_only"/>
229+
```
230+
231+
Or via environment variable (takes priority over `phpunit.xml`):
232+
233+
```bash
234+
OPENAPI_CONSOLE_OUTPUT=uncovered_only vendor/bin/phpunit
235+
```
236+
182237
## CI Integration
183238

184239
### GitHub Actions Step Summary
@@ -201,6 +256,15 @@ Use the `output_file` parameter to write a Markdown report to a file. This is us
201256
</extensions>
202257
```
203258

259+
You can also use the `OPENAPI_CONSOLE_OUTPUT` environment variable in CI to show uncovered endpoints in the job log:
260+
261+
```yaml
262+
- name: Run tests (show uncovered endpoints)
263+
run: vendor/bin/phpunit
264+
env:
265+
OPENAPI_CONSOLE_OUTPUT: uncovered_only
266+
```
267+
204268
Example GitHub Actions workflow step to post the report as a PR comment:
205269
206270
```yaml
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Studio\OpenApiContractTesting\PHPUnit;
6+
7+
use function count;
8+
use function round;
9+
use function str_repeat;
10+
11+
/**
12+
* @phpstan-type CoverageResult array{covered: string[], uncovered: string[], total: int, coveredCount: int}
13+
*/
14+
final class ConsoleCoverageRenderer
15+
{
16+
/**
17+
* @param array<string, CoverageResult> $results
18+
*/
19+
public static function render(array $results, ConsoleOutput $consoleOutput = ConsoleOutput::DEFAULT): string
20+
{
21+
if ($results === []) {
22+
return '';
23+
}
24+
25+
$output = "\n\n";
26+
$output .= "OpenAPI Contract Test Coverage\n";
27+
$output .= str_repeat('=', 50) . "\n";
28+
29+
foreach ($results as $spec => $result) {
30+
$percentage = self::percentage($result['coveredCount'], $result['total']);
31+
32+
$output .= "\n[{$spec}] {$result['coveredCount']}/{$result['total']} endpoints ({$percentage}%)\n";
33+
$output .= str_repeat('-', 50) . "\n";
34+
35+
$output .= self::renderCovered($result, $consoleOutput);
36+
$output .= self::renderUncovered($result, $consoleOutput);
37+
}
38+
39+
$output .= "\n";
40+
41+
return $output;
42+
}
43+
44+
/**
45+
* @param CoverageResult $result
46+
*/
47+
private static function renderCovered(array $result, ConsoleOutput $consoleOutput): string
48+
{
49+
if ($result['covered'] === []) {
50+
return '';
51+
}
52+
53+
if ($consoleOutput === ConsoleOutput::UNCOVERED_ONLY) {
54+
return "Covered: {$result['coveredCount']} endpoints\n";
55+
}
56+
57+
$output = "Covered:\n";
58+
59+
foreach ($result['covered'] as $endpoint) {
60+
$output .= "{$endpoint}\n";
61+
}
62+
63+
return $output;
64+
}
65+
66+
/**
67+
* @param CoverageResult $result
68+
*/
69+
private static function renderUncovered(array $result, ConsoleOutput $consoleOutput): string
70+
{
71+
if ($result['uncovered'] === []) {
72+
return '';
73+
}
74+
75+
$uncoveredCount = count($result['uncovered']);
76+
77+
if ($consoleOutput === ConsoleOutput::DEFAULT) {
78+
return "Uncovered: {$uncoveredCount} endpoints\n";
79+
}
80+
81+
$output = "Uncovered:\n";
82+
83+
foreach ($result['uncovered'] as $endpoint) {
84+
$output .= "{$endpoint}\n";
85+
}
86+
87+
return $output;
88+
}
89+
90+
private static function percentage(int $covered, int $total): float|int
91+
{
92+
return $total > 0 ? round($covered / $total * 100, 1) : 0;
93+
}
94+
}

src/PHPUnit/ConsoleOutput.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Studio\OpenApiContractTesting\PHPUnit;
6+
7+
use const STDERR;
8+
9+
use function fwrite;
10+
use function getenv;
11+
use function mb_strtolower;
12+
use function trim;
13+
14+
enum ConsoleOutput: string
15+
{
16+
case DEFAULT = 'default';
17+
case ALL = 'all';
18+
case UNCOVERED_ONLY = 'uncovered_only';
19+
20+
/**
21+
* Resolve the console output mode from environment variable and/or phpunit.xml parameter.
22+
*
23+
* Priority: environment variable > parameter > DEFAULT.
24+
*/
25+
public static function resolve(?string $parameterValue): self
26+
{
27+
$envValue = getenv('OPENAPI_CONSOLE_OUTPUT');
28+
29+
if ($envValue !== false && trim($envValue) !== '') {
30+
$resolved = self::tryFrom(mb_strtolower(trim($envValue)));
31+
32+
if ($resolved === null) {
33+
fwrite(STDERR, "[OpenAPI Coverage] WARNING: Invalid OPENAPI_CONSOLE_OUTPUT value '{$envValue}'. Valid values: default, all, uncovered_only. Falling back to 'default'.\n");
34+
}
35+
36+
return $resolved ?? self::DEFAULT;
37+
}
38+
39+
if ($parameterValue !== null && trim($parameterValue) !== '') {
40+
$resolved = self::tryFrom(mb_strtolower(trim($parameterValue)));
41+
42+
if ($resolved === null) {
43+
fwrite(STDERR, "[OpenAPI Coverage] WARNING: Invalid console_output parameter '{$parameterValue}'. Valid values: default, all, uncovered_only. Falling back to 'default'.\n");
44+
}
45+
46+
return $resolved ?? self::DEFAULT;
47+
}
48+
49+
return self::DEFAULT;
50+
}
51+
}

src/PHPUnit/OpenApiCoverageExtension.php

Lines changed: 7 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,6 @@
2323
use function fwrite;
2424
use function getcwd;
2525
use function getenv;
26-
use function round;
27-
use function str_repeat;
2826
use function str_starts_with;
2927

3028
final class OpenApiCoverageExtension implements Extension
@@ -59,15 +57,20 @@ public function bootstrap(Configuration $configuration, Facade $facade, Paramete
5957
}
6058
}
6159

60+
$consoleOutput = ConsoleOutput::resolve(
61+
$parameters->has('console_output') ? $parameters->get('console_output') : null,
62+
);
63+
6264
$githubSummaryPath = getenv('GITHUB_STEP_SUMMARY') ?: null;
6365

64-
$facade->registerSubscriber(new class ($specs, $outputFile, $githubSummaryPath) implements ExecutionFinishedSubscriber {
66+
$facade->registerSubscriber(new class ($specs, $outputFile, $consoleOutput, $githubSummaryPath) implements ExecutionFinishedSubscriber {
6567
/**
6668
* @param string[] $specs
6769
*/
6870
public function __construct(
6971
private readonly array $specs,
7072
private readonly ?string $outputFile,
73+
private readonly ConsoleOutput $consoleOutput,
7174
private readonly ?string $githubSummaryPath,
7275
) {}
7376

@@ -80,7 +83,7 @@ public function notify(ExecutionFinished $event): void
8083
return;
8184
}
8285

83-
$this->printReport($results);
86+
echo ConsoleCoverageRenderer::render($results, $this->consoleOutput);
8487

8588
if ($this->outputFile !== null || $this->githubSummaryPath !== null) {
8689
$this->writeMarkdownReport($results);
@@ -122,39 +125,6 @@ private function computeAllResults(): array
122125
return $results;
123126
}
124127

125-
/**
126-
* @param array<string, array{covered: string[], uncovered: string[], total: int, coveredCount: int}> $results
127-
*/
128-
private function printReport(array $results): void
129-
{
130-
echo "\n\n";
131-
echo "OpenAPI Contract Test Coverage\n";
132-
echo str_repeat('=', 50) . "\n";
133-
134-
foreach ($results as $spec => $result) {
135-
$percentage = self::percentage($result['coveredCount'], $result['total']);
136-
137-
echo "\n[{$spec}] {$result['coveredCount']}/{$result['total']} endpoints ({$percentage}%)\n";
138-
echo str_repeat('-', 50) . "\n";
139-
140-
if ($result['covered'] !== []) {
141-
echo "Covered:\n";
142-
143-
foreach ($result['covered'] as $endpoint) {
144-
echo "{$endpoint}\n";
145-
}
146-
}
147-
148-
$uncoveredCount = $result['total'] - $result['coveredCount'];
149-
150-
if ($uncoveredCount > 0) {
151-
echo "Uncovered: {$uncoveredCount} endpoints\n";
152-
}
153-
}
154-
155-
echo "\n";
156-
}
157-
158128
/**
159129
* @param array<string, array{covered: string[], uncovered: string[], total: int, coveredCount: int}> $results
160130
*/
@@ -178,11 +148,6 @@ private function writeMarkdownReport(array $results): void
178148
}
179149
}
180150
}
181-
182-
private static function percentage(int $covered, int $total): float|int
183-
{
184-
return $total > 0 ? round($covered / $total * 100, 1) : 0;
185-
}
186151
});
187152
}
188153
}

0 commit comments

Comments
 (0)