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