packages/requirements/tests/Unit/Report/SourceUnitTest.php
1<?php
2
3declare(strict_types=1);
4
5namespace Tests\Unit\Report;
6
7use JsonException;
8use PHPUnit\Framework\Attributes\CoversClass;
9use PHPUnit\Framework\Attributes\Small;
10use PHPUnit\Framework\Attributes\UsesClass;
11use PHPUnit\Framework\TestCase;
12use Requirements\Model\Item;
13use Requirements\Model\Source;
14use Requirements\Report\SourceUnit;
15use Requirements\Source\Unit;
16
17#[CoversClass(SourceUnit::class)]
18#[UsesClass(Item::class)]
19#[UsesClass(Source::class)]
20#[UsesClass(Unit::class)]
21#[Small]
22final class SourceUnitTest extends TestCase
23{
24 public function testSupportedIsFalseWithoutClaims(): void
25 {
26 $unit = new SourceUnit(new Source('manual', 'source.html', 'html', 'main p'), new Unit('p:1', 'Names start with a letter.'));
27 self::assertFalse($unit->supported());
28 }
29
30 public function testSupportedIsFalseWithOnlyUnsupportedClaims(): void
31 {
32 $source = new Source('manual', 'source.html', 'html', 'main p');
33 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
34 $unit->claims = [
35 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall read names.', 'unsupported', $source, [], [], [], [], [], '', 'sourced', 'Out of scope.', 'definition.yaml', []),
36 'SPEC-002' => new Item('SPEC-002', 'specification', 'The parser shall read digits.', 'unsupported', $source, [], [], [], [], [], '', 'sourced', 'Out of scope.', 'definition.yaml', []),
37 ];
38 self::assertFalse($unit->supported());
39 }
40
41 public function testSupportedIsTrueWhenAnyClaimIsSupported(): void
42 {
43 $source = new Source('manual', 'source.html', 'html', 'main p');
44 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
45 $unit->claims = [
46 'SPEC-001' => new Item('SPEC-001', 'specification', 'The parser shall read names.', 'unsupported', $source, [], [], [], [], [], '', 'sourced', 'Out of scope.', 'definition.yaml', []),
47 'SPEC-002' => new Item('SPEC-002', 'specification', 'The parser shall read digits.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', []),
48 ];
49 self::assertTrue($unit->supported());
50 }
51
52 /**
53 * @throws JsonException
54 */
55 public function testFingerprintHashesTheTextWithoutClaims(): void
56 {
57 $unit = new SourceUnit(new Source('manual', 'source.html', 'html', 'main p'), new Unit('p:1', 'Names start with a letter.'));
58 self::assertSame(hash('sha256', '["Names start with a letter.",[]]'), $unit->fingerprint([]));
59 }
60
61 /**
62 * @throws JsonException
63 */
64 public function testFingerprintHashesClaimsAndRefinedRequirementsByID(): void
65 {
66 $source = new Source('manual', 'source.html', 'html', 'main p');
67 $requirement = new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'REQ-001']);
68 $specification = new Item('SPEC-001', 'specification', 'The parser shall read names.', 'supported', $source, [], [], ['REQ-001'], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'SPEC-001', 'requirements' => ['REQ-001']]);
69 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
70 $unit->claims = ['SPEC-001' => $specification];
71 self::assertSame(hash('sha256', '["Names start with a letter.",{"REQ-001":{"id":"REQ-001"},"SPEC-001":{"id":"SPEC-001","requirements":["REQ-001"]}}]'), $unit->fingerprint(['REQ-001' => $requirement, 'SPEC-001' => $specification]));
72 }
73
74 /**
75 * @throws JsonException
76 */
77 public function testFingerprintIgnoresTheOrderOfClaims(): void
78 {
79 $source = new Source('manual', 'source.html', 'html', 'main p');
80 $first = new Item('SPEC-001', 'specification', 'The parser shall read names.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'SPEC-001']);
81 $second = new Item('SPEC-002', 'specification', 'The parser shall read digits.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'SPEC-002']);
82 $forward = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
83 $forward->claims = ['SPEC-001' => $first, 'SPEC-002' => $second];
84 $backward = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
85 $backward->claims = ['SPEC-002' => $second, 'SPEC-001' => $first];
86 self::assertSame($forward->fingerprint([]), $backward->fingerprint([]));
87 }
88
89 /**
90 * @throws JsonException
91 */
92 public function testFingerprintChangesWithTheRefinedRequirementRecord(): void
93 {
94 $source = new Source('manual', 'source.html', 'html', 'main p');
95 $specification = new Item('SPEC-001', 'specification', 'The parser shall read names.', 'supported', $source, [], [], ['REQ-001'], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'SPEC-001']);
96 $before = new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'REQ-001']);
97 $after = new Item('REQ-001', 'requirement', 'Names start with a letter.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'REQ-001', 'labels' => ['names']]);
98 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
99 $unit->claims = ['SPEC-001' => $specification];
100 self::assertNotSame($unit->fingerprint(['REQ-001' => $before]), $unit->fingerprint(['REQ-001' => $after]));
101 }
102
103 /**
104 * @throws JsonException
105 */
106 public function testToArrayReportsAnUncoveredUnit(): void
107 {
108 $unit = new SourceUnit(new Source('manual', 'source.html', 'html', 'main p'), new Unit('p:1', 'Names start with a letter.'));
109 self::assertSame(['uri' => 'source.html', 'format' => 'html', 'location' => 'p:1', 'text' => 'Names start with a letter.', 'claims' => [], 'status' => 'uncovered', 'fingerprint' => hash('sha256', '["Names start with a letter.",[]]')], $unit->toArray([]));
110 }
111
112 /**
113 * @throws JsonException
114 */
115 public function testToArrayReportsAnUnsupportedUnit(): void
116 {
117 $source = new Source('manual', 'source.html', 'html', 'main p');
118 $item = new Item('SPEC-001', 'specification', 'The parser shall read names.', 'unsupported', $source, [], [], [], [], [], '', 'sourced', 'Out of scope.', 'definition.yaml', ['id' => 'SPEC-001']);
119 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
120 $unit->claims = ['SPEC-001' => $item];
121 self::assertSame(['uri' => 'source.html', 'format' => 'html', 'location' => 'p:1', 'text' => 'Names start with a letter.', 'claims' => ['SPEC-001'], 'status' => 'unsupported', 'fingerprint' => hash('sha256', '["Names start with a letter.",{"SPEC-001":{"id":"SPEC-001"}}]')], $unit->toArray(['SPEC-001' => $item]));
122 }
123
124 /**
125 * @throws JsonException
126 */
127 public function testToArrayReportsASupportedUnit(): void
128 {
129 $source = new Source('manual', 'source.html', 'html', 'main p');
130 $first = new Item('SPEC-001', 'specification', 'The parser shall read names.', 'unsupported', $source, [], [], [], [], [], '', 'sourced', 'Out of scope.', 'definition.yaml', ['id' => 'SPEC-001']);
131 $second = new Item('SPEC-002', 'specification', 'The parser shall read digits.', 'supported', $source, [], [], [], [], [], '', 'sourced', '', 'definition.yaml', ['id' => 'SPEC-002']);
132 $unit = new SourceUnit($source, new Unit('p:1', 'Names start with a letter.'));
133 $unit->claims = ['SPEC-001' => $first, 'SPEC-002' => $second];
134 $record = $unit->toArray([]);
135 self::assertSame(['SPEC-001', 'SPEC-002'], $record['claims']);
136 self::assertSame('supported', $record['status']);
137 }
138}
139