Skip to content

Commit 9d46dba

Browse files
committed
TASK: Increase test coverage
and add code annotations to satisfy linter
1 parent b91781f commit 9d46dba

20 files changed

+1488
-7
lines changed

.github/workflows/php.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
uses: shivammathur/setup-php@v2
2424
with:
2525
php-version: ${{ matrix.php-versions }}
26+
coverage: xdebug
2627

2728
- name: Checkout code
2829
uses: actions/checkout@v3
@@ -44,3 +45,6 @@ jobs:
4445

4546
- name: Run test suite
4647
run: composer run-script test
48+
49+
- name: Check code coverage (100% required)
50+
run: composer run-script test:unit:coverage:check

check-coverage.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/**
6+
* Check code coverage and fail if below 100%
7+
*/
8+
9+
$coverageFile = __DIR__ . '/coverage.xml';
10+
11+
if (!file_exists($coverageFile)) {
12+
echo "Coverage file not found: {$coverageFile}\n";
13+
echo "Run: composer run-script test:unit:coverage\n";
14+
exit(1);
15+
}
16+
17+
$xml = simplexml_load_string(file_get_contents($coverageFile));
18+
if ($xml === false) {
19+
echo "Failed to parse coverage file\n";
20+
exit(1);
21+
}
22+
23+
$metrics = $xml->project->metrics;
24+
$statements = (int) $metrics['statements'];
25+
$coveredStatements = (int) $metrics['coveredstatements'];
26+
27+
if ($statements === 0) {
28+
echo "No statements found in coverage report\n";
29+
exit(1);
30+
}
31+
32+
$coverage = ($coveredStatements / $statements) * 100;
33+
34+
if ($coverage < 100) {
35+
echo sprintf(
36+
"✗ Code coverage is %.2f%%, which is below the required 100%%\n",
37+
$coverage,
38+
);
39+
echo sprintf(
40+
" Covered: %d/%d statements\n",
41+
$coveredStatements,
42+
$statements,
43+
);
44+
exit(1);
45+
}
46+
47+
echo sprintf("✓ Code coverage is 100%% (%d/%d statements)\n", $coveredStatements, $statements);
48+
exit(0);

composer.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,13 @@
4444
"test:phpstan": "phpstan",
4545
"test:cs": "vendor/bin/php-cs-fixer check",
4646
"test:cs:fix": "vendor/bin/php-cs-fixer fix",
47-
"test:unit": "phpunit tests/Unit",
48-
"test:integration": "phpunit tests/Integration --exclude-group=feature_liveStream",
47+
"test:unit": "phpunit tests/Unit --no-coverage",
48+
"test:unit:coverage": "php -d xdebug.mode=coverage vendor/bin/phpunit tests/Unit --coverage-text --coverage-clover=coverage.xml --coverage-filter=src",
49+
"test:unit:coverage:check": [
50+
"@test:unit:coverage",
51+
"@php check-coverage.php"
52+
],
53+
"test:integration": "phpunit tests/Integration --exclude-group=feature_liveStream --no-coverage",
4954
"test": [
5055
"@test:phpstan",
5156
"@test:cs",

phpunit.xml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,25 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3-
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.5/phpunit.xsd"
44
bootstrap="vendor/autoload.php"
55
cacheDirectory=".phpunit.cache"
66
executionOrder="depends,defects"
77
requireCoverageMetadata="true"
8-
beStrictAboutCoverageMetadata="true"
98
beStrictAboutOutputDuringTests="true"
10-
failOnRisky="true"
11-
failOnWarning="true">
9+
failOnRisky="true">
1210
<testsuites>
1311
<testsuite name="default">
1412
<directory>tests</directory>
1513
</testsuite>
1614
</testsuites>
1715

18-
<coverage includeUncoveredFiles="false"></coverage>
16+
<coverage includeUncoveredFiles="true"
17+
ignoreDeprecatedCodeUnits="true"
18+
disableCodeCoverageIgnore="false">
19+
<report>
20+
<text outputFile="php://stdout"/>
21+
</report>
22+
</coverage>
1923

2024
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
2125
<include>

src/Query/QueryItem.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ public function with(
7272

7373
public function canBeMerged(self $other): bool
7474
{
75+
/** @noinspection TypeUnsafeComparisonInspection */
76+
/** @noinspection PhpNonStrictObjectEqualityInspection */
7577
return $this->onlyLastEvent === $other->onlyLastEvent && $this->tags == $other->tags;
7678
}
7779

@@ -80,6 +82,8 @@ public function merge(self $other): self
8082
if ($this->onlyLastEvent !== $other->onlyLastEvent) {
8183
throw new InvalidArgumentException('Query items with different values for "onlyLasEvent" flag cannot be merged');
8284
}
85+
/** @noinspection TypeUnsafeComparisonInspection */
86+
/** @noinspection PhpNonStrictObjectEqualityInspection */
8387
if ($this->tags != $other->tags) {
8488
throw new InvalidArgumentException('Query items with tag mismatch cannot be merged');
8589
}

src/SequencedEvent/SequencedEvents.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public function first(): SequencedEvent|null
6464
if ($this->first !== null) {
6565
return $this->first;
6666
}
67+
/** @noinspection LoopWhichDoesNotLoopInspection */
6768
foreach ($this as $sequencedEvent) {
6869
return $sequencedEvent;
6970
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unit\AppendCondition;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\Medium;
9+
use PHPUnit\Framework\TestCase;
10+
use Wwwision\DCBEventStore\AppendCondition\AppendCondition;
11+
use Wwwision\DCBEventStore\Query\Query;
12+
use Wwwision\DCBEventStore\Query\QueryItem;
13+
use Wwwision\DCBEventStore\SequencedEvent\SequencePosition;
14+
15+
#[CoversClass(AppendCondition::class)]
16+
#[Medium]
17+
final class AppendConditionTest extends TestCase
18+
{
19+
public function test_constructor_sets_properties(): void
20+
{
21+
$query = Query::fromItems(QueryItem::create(eventTypes: 'SomeType'));
22+
$position = SequencePosition::fromInteger(10);
23+
24+
$condition = new AppendCondition($query, $position);
25+
26+
self::assertSame($query, $condition->failIfEventsMatch);
27+
self::assertSame($position, $condition->after);
28+
}
29+
30+
public function test_constructor_with_null_after(): void
31+
{
32+
$query = Query::fromItems(QueryItem::create(tags: 'some-tag'));
33+
34+
$condition = new AppendCondition($query, null);
35+
36+
self::assertSame($query, $condition->failIfEventsMatch);
37+
self::assertNull($condition->after);
38+
}
39+
40+
public function test_properties_are_readonly(): void
41+
{
42+
$query = Query::fromItems(QueryItem::create(eventTypes: 'TestType'));
43+
$position = SequencePosition::fromInteger(5);
44+
45+
$condition = new AppendCondition($query, $position);
46+
47+
self::assertSame($query, $condition->failIfEventsMatch);
48+
self::assertSame($position, $condition->after);
49+
}
50+
}

tests/Unit/Event/EventDataTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unit\Event;
6+
7+
use InvalidArgumentException;
8+
use PHPUnit\Framework\Attributes\CoversClass;
9+
use PHPUnit\Framework\Attributes\Medium;
10+
use PHPUnit\Framework\TestCase;
11+
use RuntimeException;
12+
use Wwwision\DCBEventStore\Event\EventData;
13+
14+
#[Medium]
15+
#[CoversClass(EventData::class)]
16+
final class EventDataTest extends TestCase
17+
{
18+
public function test_fromString_allows_empty_string(): void
19+
{
20+
$eventData = EventData::fromString('');
21+
self::assertSame('', $eventData->value);
22+
}
23+
24+
public function test_jsonDecode_fails_if_data_is_no_valid_json(): void
25+
{
26+
$eventData = EventData::fromString('not json');
27+
$this->expectException(RuntimeException::class);
28+
$this->expectExceptionMessage('Failed to JSON-decode event data: Syntax error');
29+
$eventData->jsonDecode();
30+
}
31+
32+
public function test_jsonDecode_fails_if_data_is_no_json_array(): void
33+
{
34+
$eventData = EventData::fromString('true');
35+
$this->expectException(InvalidArgumentException::class);
36+
$this->expectExceptionMessage('Expected an array. Got: boolean');
37+
$eventData->jsonDecode();
38+
}
39+
40+
public function test_jsonDecode_returns_encoded_array_data(): void
41+
{
42+
$data = ['foo' => 'bar', 'bar' => ['baz' => true, 'foos' => 123.45]];
43+
$dataJson = json_encode($data, JSON_THROW_ON_ERROR);
44+
self::assertIsString($dataJson);
45+
$result = EventData::fromString($dataJson)->jsonDecode();
46+
self::assertSame($data, $result);
47+
}
48+
49+
public function test_json_represents_value(): void
50+
{
51+
$eventData = EventData::fromString('input');
52+
self::assertSame('"input"', json_encode($eventData, JSON_THROW_ON_ERROR));
53+
}
54+
}

tests/Unit/Event/EventMetadataTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,26 @@ public function test_none_creates_empty_instance(): void
1717
self::assertSame([], EventMetadata::none()->value);
1818
}
1919

20+
public function test_fromArray_creates_instance_from_associative_array(): void
21+
{
22+
$metadata = EventMetadata::fromArray(['key1' => 'value1', 'key2' => 'value2']);
23+
24+
self::assertSame(['key1' => 'value1', 'key2' => 'value2'], $metadata->value);
25+
}
26+
2027
public function test_fromArray_fails_if_array_is_not_associative(): void
2128
{
2229
$this->expectException(InvalidArgumentException::class);
2330
EventMetadata::fromArray(['foo', 'bar']);
2431
}
2532

33+
public function test_fromJson_creates_instance_from_valid_json(): void
34+
{
35+
$metadata = EventMetadata::fromJson('{"key1": "value1", "key2": "value2"}');
36+
37+
self::assertSame(['key1' => 'value1', 'key2' => 'value2'], $metadata->value);
38+
}
39+
2640
public function test_fromJson_fails_if_value_is_no_valid_json(): void
2741
{
2842
$this->expectException(InvalidArgumentException::class);

tests/Unit/Event/EventTypeTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Unit\Event;
6+
7+
use PHPUnit\Framework\Attributes\CoversClass;
8+
use PHPUnit\Framework\Attributes\Medium;
9+
use PHPUnit\Framework\TestCase;
10+
use Wwwision\DCBEventStore\Event\EventType;
11+
12+
#[Medium]
13+
#[CoversClass(EventType::class)]
14+
final class EventTypeTest extends TestCase
15+
{
16+
public function test_equal_returns_true_if_event_types_match(): void
17+
{
18+
$eventType1 = EventType::fromString('some-type');
19+
$eventType2 = EventType::fromString('some-type');
20+
self::assertTrue($eventType1->equals($eventType2));
21+
}
22+
23+
public function test_equal_returns_false_if_event_types_do_not_match(): void
24+
{
25+
$eventType1 = EventType::fromString('some-type');
26+
$eventType2 = EventType::fromString('Some-Type');
27+
self::assertFalse($eventType1->equals($eventType2));
28+
}
29+
30+
public function test_jsonSerialize_returns_string_value(): void
31+
{
32+
$eventType = EventType::fromString('TestType');
33+
34+
self::assertSame('TestType', $eventType->jsonSerialize());
35+
}
36+
}

0 commit comments

Comments
 (0)