packages/requirements/tests/Integration/Console/SpecPolicyTest.php

1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Integration\Console;
6
7use JsonException;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\DataProvider;
10use PHPUnit\Framework\Attributes\Large;
11use PHPUnit\Framework\Attributes\UsesClass;
12use PHPUnit\Framework\TestCase;
13use Requirements\Console\Application;
14use Requirements\Input\Fields;
15use Tests\Fake\CommandLine as Cli;
16use Tests\Fake\CountingRunner;
17use Tests\Fake\ProjectDirectory;
18
19#[CoversClass(Application::class)]
20#[UsesClass(Fields::class)]
21#[Large]
22final class SpecPolicyTest extends TestCase
23{
24    /**
25     * @param list<array<string, mixed>> $items
26     * @param list<string> $options
27     * @param array<string, string> $statuses
28     * @throws JsonException
29     */
30    #[DataProvider('providerPolicies')]
31    public function testPoliciesControlExitCodesAndExecutedTargets(array $items, array $options, int $exitCode, array $statuses, string $executed): void
32    {
33        $project = new ProjectDirectory();
34        $project->write('requirements.yaml', [
35            'version' => 1,
36            'definitions' => ['definition.yaml'],
37            'extensions' => ['runners' => ['counting' => CountingRunner::class]],
38            'runners' => ['unit' => ['extension' => 'counting', 'command' => ['unused']]],
39        ]);
40        $project->write('definition.yaml', ['version' => 1, 'source' => ['id' => 'manual', 'uri' => 'source.html', 'format' => 'html', 'selector' => 'main p'], 'items' => $items]);
41        $process = Cli::run(['spec', '--json', ...$options], $project->directory);
42        self::assertSame($exitCode, $process->getExitCode(), $process->getOutput() . $process->getErrorOutput());
43        $report = Fields::mapping(json_decode($process->getOutput(), true, 512, JSON_THROW_ON_ERROR), 'report');
44        self::assertSame($exitCode === 0, $report['passed']);
45        self::assertSame($statuses, array_column(Fields::mapping($report['specifications'], 'rows'), 'status', 'id'));
46        self::assertSame(in_array('--all', $options, true), $report['all']);
47        self::assertSame(in_array('--strict', $options, true), $report['strict']);
48        self::assertSame($executed, $project->read('executions.txt'));
49        self::assertSame($executed !== '', is_file($project->path('executions.txt')));
50    }
51
52    /**
53     * @return array<string, array{list<array<string, mixed>>, list<string>, int, array<string, string>, string}>
54     */
55    public static function providerPolicies(): array
56    {
57        $base = ProjectDirectory::item();
58        $unlinked = [...$base, 'id' => 'UNLINKED'];
59        $unit = ['runner' => 'unit', 'target' => 'fast'];
60        $manual = [...$unit, 'run' => 'manual'];
61        $automatic = [...$base, 'id' => 'AUTO', 'tests' => [$unit, $manual]];
62        $deferred = [...$base, 'id' => 'MANUAL', 'tests' => [$manual, $manual]];
63        $mixed = [...$base, 'id' => 'MIXED', 'tests' => [$unit, ['runner' => 'unit', 'target' => 'empty', 'run' => 'manual']]];
64        $failure = [...$base, 'id' => 'FAILURE', 'tests' => [['runner' => 'unit', 'target' => 'empty'], $manual]];
65        $unsupported = [...$base, 'id' => 'UNSUPPORTED', 'status' => 'unsupported', 'reason' => 'Not implemented.', 'tests' => [['runner' => 'unit', 'target' => 'never', 'run' => 'manual']]];
66        $requirement = [...$base, 'id' => 'REQ', 'kind' => 'requirement'];
67        return [
68            'unlinked allowed' => [[$unlinked], [], 0, ['UNLINKED' => 'unverified'], ''],
69            'strict unlinked' => [[$unlinked], ['--strict'], 1, ['UNLINKED' => 'unverified'], ''],
70            'all does not invent links' => [[$unlinked], ['--all'], 0, ['UNLINKED' => 'unverified'], ''],
71            'all strict still needs links' => [[$unlinked], ['--all', '--strict'], 1, ['UNLINKED' => 'unverified'], ''],
72            'no test unlinked' => [[$unlinked], ['--no-test'], 0, ['UNLINKED' => 'not-run'], ''],
73            'no test strict' => [[$unlinked], ['--no-test', '--strict'], 1, ['UNLINKED' => 'not-run'], ''],
74            'empty selection' => [[$unlinked], ['--id=MISSING'], 0, [], ''],
75            'strict empty selection' => [[$unlinked], ['--id=MISSING', '--strict'], 1, [], ''],
76            'manual only' => [[$deferred], [], 0, ['MANUAL' => 'deferred'], ''],
77            'strict permits manual' => [[$deferred], ['--strict'], 0, ['MANUAL' => 'deferred'], ''],
78            'all executes manual once' => [[$deferred], ['--all', '--strict'], 0, ['MANUAL' => 'passed'], "fast\n"],
79            'mixed deferred' => [[$mixed], [], 0, ['MIXED' => 'deferred'], "fast\n"],
80            'all exposes failure' => [[$mixed], ['--all'], 1, ['MIXED' => 'failed'], "fast\nempty\n"],
81            'failure beats deferred' => [[$failure], [], 1, ['FAILURE' => 'failed'], "empty\n"],
82            'no test beats all' => [[$mixed], ['--no-test', '--all', '--strict'], 0, ['MIXED' => 'not-run'], ''],
83            'shared manual first' => [[$deferred, $automatic], [], 0, ['MANUAL' => 'passed', 'AUTO' => 'passed'], "fast\n"],
84            'shared automatic first' => [[$automatic, $deferred], [], 0, ['AUTO' => 'passed', 'MANUAL' => 'passed'], "fast\n"],
85            'filter excludes automatic reference' => [[$automatic, $deferred], ['--id=MANUAL'], 0, ['MANUAL' => 'deferred'], ''],
86            'all respects filter' => [[$failure, $deferred], ['--id=MANUAL', '--all'], 0, ['MANUAL' => 'passed'], "fast\n"],
87            'all respects support' => [[$unsupported, $requirement], ['--all', '--strict'], 0, ['UNSUPPORTED' => 'unsupported', 'REQ' => 'not-applicable'], ''],
88        ];
89    }
90}
91