packages/requirements/tests/Unit/Config/LinkValidatorTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Config;
6
7use PHPUnit\Framework\Attributes\CoversClass;
8use PHPUnit\Framework\Attributes\Small;
9use PHPUnit\Framework\Attributes\UsesClass;
10use PHPUnit\Framework\TestCase;
11use Requirements\Config\LinkValidator;
12use Requirements\Input\InvalidInputException;
13use Requirements\Model\Item;
14use Requirements\Model\TestReference;
15use Requirements\Test\RunnerConfig;
16
17#[CoversClass(LinkValidator::class)]
18#[UsesClass(Item::class)]
19#[UsesClass(RunnerConfig::class)]
20#[UsesClass(TestReference::class)]
21#[Small]
22final class LinkValidatorTest extends TestCase
23{
24 public function testValidateAcceptsResolvedLinks(): void
25 {
26 $items = [
27 'REQ-001' => new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', null, [], [], [], [], [], '', 'sourced', '', 'a.yaml', []),
28 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [new TestReference('unit', 'Test::testPass')], ['REQ-001'], ['SPEC-002'], [], '', 'sourced', '', 'b.yaml', []),
29 'SPEC-002' => new Item('SPEC-002', 'specification', 'The parser shall accept digits.', 'supported', null, [], [], [], ['SPEC-001', 'REQ-001'], [], '', 'original', 'Design choice.', 'b.yaml', []),
30 ];
31 (new LinkValidator())->validate($items, ['unit' => new RunnerConfig('phpunit', ['phpunit'], '/project')]);
32 $this->addToAssertionCount(1);
33 }
34
35 public function testValidateAcceptsNoItems(): void
36 {
37 (new LinkValidator())->validate([], []);
38 $this->addToAssertionCount(1);
39 }
40
41 public function testValidateRejectsMissingRequirement(): void
42 {
43 $items = ['SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], ['MISSING'], [], [], '', 'sourced', '', 'b.yaml', [])];
44 $this->expectException(InvalidInputException::class);
45 $this->expectExceptionMessage("SPEC-001: missing or self reference 'MISSING'.");
46 (new LinkValidator())->validate($items, []);
47 }
48
49 public function testValidateRejectsMissingRelatedItem(): void
50 {
51 $items = ['SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], [], ['SPEC-404'], [], '', 'sourced', '', 'b.yaml', [])];
52 $this->expectException(InvalidInputException::class);
53 $this->expectExceptionMessage("SPEC-001: missing or self reference 'SPEC-404'.");
54 (new LinkValidator())->validate($items, []);
55 }
56
57 public function testValidateRejectsSelfRelation(): void
58 {
59 $items = ['SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], [], ['SPEC-001'], [], '', 'sourced', '', 'b.yaml', [])];
60 $this->expectException(InvalidInputException::class);
61 $this->expectExceptionMessage("SPEC-001: missing or self reference 'SPEC-001'.");
62 (new LinkValidator())->validate($items, []);
63 }
64
65 public function testValidateRejectsSelfRequirement(): void
66 {
67 $items = ['REQ-001' => new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', null, [], [], ['REQ-001'], [], [], '', 'sourced', '', 'a.yaml', [])];
68 $this->expectException(InvalidInputException::class);
69 $this->expectExceptionMessage("REQ-001: missing or self reference 'REQ-001'.");
70 (new LinkValidator())->validate($items, []);
71 }
72
73 public function testValidateRejectsRequirementThatIsSpecification(): void
74 {
75 $items = [
76 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], ['SPEC-002'], [], [], '', 'sourced', '', 'b.yaml', []),
77 'SPEC-002' => new Item('SPEC-002', 'specification', 'The parser shall accept digits.', 'supported', null, [], [], [], [], [], '', 'sourced', '', 'b.yaml', []),
78 ];
79 $this->expectException(InvalidInputException::class);
80 $this->expectExceptionMessage("SPEC-001: 'SPEC-002' must be a sourced requirement.");
81 (new LinkValidator())->validate($items, []);
82 }
83
84 public function testValidateRejectsRequirementThatIsNotSourced(): void
85 {
86 $items = [
87 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], ['REQ-001'], [], [], '', 'sourced', '', 'b.yaml', []),
88 'REQ-001' => new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', null, [], [], [], [], [], '', 'original', 'Local rule.', 'a.yaml', []),
89 ];
90 $this->expectException(InvalidInputException::class);
91 $this->expectExceptionMessage("SPEC-001: 'REQ-001' must be a sourced requirement.");
92 (new LinkValidator())->validate($items, []);
93 }
94
95 public function testValidateRejectsUnknownRunner(): void
96 {
97 $items = ['SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [new TestReference('unit', 'Test::testPass'), new TestReference('missing', 'Test::testPass')], [], [], [], '', 'sourced', '', 'b.yaml', [])];
98 $this->expectException(InvalidInputException::class);
99 $this->expectExceptionMessage("SPEC-001: unknown runner 'missing'.");
100 (new LinkValidator())->validate($items, ['unit' => new RunnerConfig('phpunit', ['phpunit'], '/project')]);
101 }
102
103 public function testValidateRejectsRunnerNamedByExtension(): void
104 {
105 $items = ['SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [new TestReference('phpunit', 'Test::testPass')], [], [], [], '', 'sourced', '', 'b.yaml', [])];
106 $this->expectException(InvalidInputException::class);
107 $this->expectExceptionMessage("SPEC-001: unknown runner 'phpunit'.");
108 (new LinkValidator())->validate($items, ['unit' => new RunnerConfig('phpunit', ['phpunit'], '/project')]);
109 }
110
111 public function testValidateChecksEveryItem(): void
112 {
113 $items = [
114 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall require a leading letter.', 'supported', null, [], [], [], [], [], '', 'sourced', '', 'b.yaml', []),
115 'SPEC-002' => new Item('SPEC-002', 'specification', 'The parser shall accept digits.', 'supported', null, [], [], [], ['SPEC-003'], [], '', 'sourced', '', 'b.yaml', []),
116 ];
117 $this->expectException(InvalidInputException::class);
118 $this->expectExceptionMessage("SPEC-002: missing or self reference 'SPEC-003'.");
119 (new LinkValidator())->validate($items, []);
120 }
121}
122