From 2013012715878c561c05106dd6bd3aacf2baf426 Mon Sep 17 00:00:00 2001 From: Martin Komischke Date: Thu, 12 Feb 2026 08:41:09 +0100 Subject: [PATCH] implement 100% coverage for the core domain --- backend/phpstan-baseline.neon | 29 +++++++++-- .../Instrumentation/InstrumentationHolder.php | 9 ++-- .../Instrumentation/InstrumentationTest.php | 5 +- .../Unit/SplitFairly/CompensationTest.php | 4 ++ backend/tests/Unit/SplitFairly/EventTest.php | 51 +++++++++++++++++++ .../Unit/SplitFairly/QueryOptionsTest.php | 43 ++++++++++++++++ 6 files changed, 130 insertions(+), 11 deletions(-) create mode 100644 backend/tests/Unit/SplitFairly/EventTest.php create mode 100644 backend/tests/Unit/SplitFairly/QueryOptionsTest.php diff --git a/backend/phpstan-baseline.neon b/backend/phpstan-baseline.neon index f552278..7a8dac7 100644 --- a/backend/phpstan-baseline.neon +++ b/backend/phpstan-baseline.neon @@ -5,14 +5,33 @@ parameters: identifier: argument.type count: 1 path: public/index.php - - - message: '#^Method App\\Normalizer\\Normalizer\:\:toArray\(\) should return array\ but returns array\|ArrayObject\|bool\|float\|int\|string\|null\.$#' - identifier: return.type - count: 1 - path: src/Normalizer/Normalizer.php - message: '#^Property App\\Entity\\User\:\:\$id \(int\|null\) is never assigned int so it can be removed from the property type\.$#' identifier: property.unusedType count: 1 path: src/Entity/User.php + + - + message: '#^Cannot call method getLogging\(\) on App\\Instrumentation\\Instrumentation\|null\.$#' + identifier: method.nonObject + count: 1 + path: src/Instrumentation/InstrumentationHolder.php + + - + message: '#^Cannot call method getMetrics\(\) on App\\Instrumentation\\Instrumentation\|null\.$#' + identifier: method.nonObject + count: 1 + path: src/Instrumentation/InstrumentationHolder.php + + - + message: '#^Cannot call method getTracing\(\) on App\\Instrumentation\\Instrumentation\|null\.$#' + identifier: method.nonObject + count: 1 + path: src/Instrumentation/InstrumentationHolder.php + + - + message: '#^Method App\\Normalizer\\Normalizer\:\:toArray\(\) should return array\ but returns array\|ArrayObject\|bool\|float\|int\|string\|null\.$#' + identifier: return.type + count: 1 + path: src/Normalizer/Normalizer.php diff --git a/backend/src/Instrumentation/InstrumentationHolder.php b/backend/src/Instrumentation/InstrumentationHolder.php index d9d0bef..f845350 100644 --- a/backend/src/Instrumentation/InstrumentationHolder.php +++ b/backend/src/Instrumentation/InstrumentationHolder.php @@ -4,6 +4,7 @@ namespace App\Instrumentation; +use App\Invariant\Ensure; use Psr\Log\LoggerInterface; final class InstrumentationHolder @@ -12,7 +13,7 @@ final class InstrumentationHolder public static function initialize(string $telemetry, LoggerInterface $logger): void { - assert(in_array($telemetry, ['PsrLog', 'Null'], strict: true), 'Either "PsrLog" or "Null" is allowed for parameter $telemetry!'); + Ensure::that(in_array($telemetry, ['PsrLog', 'Null'], strict: true), 'Either "PsrLog" or "Null" is allowed for parameter $telemetry!'); if (is_null(self::$instance)) { self::$instance = new Instrumentation($telemetry, $logger); @@ -21,21 +22,21 @@ public static function initialize(string $telemetry, LoggerInterface $logger): v public static function getLogging(): LoggingInterface { - assert(!is_null(self::$instance), 'Not yet initialized!'); + Ensure::that(!is_null(self::$instance), 'Not yet initialized!'); return self::$instance->getLogging(); } public static function getMetrics(): MetricsInterface { - assert(!is_null(self::$instance), 'Not yet initialized!'); + Ensure::that(!is_null(self::$instance), 'Not yet initialized!'); return self::$instance->getMetrics(); } public static function getTracing(): TracingInterface { - assert(!is_null(self::$instance), 'Not yet initialized!'); + Ensure::that(!is_null(self::$instance), 'Not yet initialized!'); return self::$instance->getTracing(); } diff --git a/backend/tests/Unit/Instrumentation/InstrumentationTest.php b/backend/tests/Unit/Instrumentation/InstrumentationTest.php index 2f96607..7a937d3 100644 --- a/backend/tests/Unit/Instrumentation/InstrumentationTest.php +++ b/backend/tests/Unit/Instrumentation/InstrumentationTest.php @@ -8,6 +8,7 @@ use App\Instrumentation\LoggingInterface; use App\Instrumentation\MetricsInterface; use App\Instrumentation\TracingInterface; +use App\Invariant\InvariantException; use PHPUnit\Framework\TestCase; use Psr\Log\NullLogger; @@ -79,7 +80,7 @@ public function test_provider_provides_tracing_interface(): void public function test_provider_throws_on_uninitialized_access_logging(): void { - $this->expectException(\AssertionError::class); + $this->expectException(InvariantException::class); $this->expectExceptionMessage('Not yet initialized!'); InstrumentationHolder::getLogging(); @@ -87,7 +88,7 @@ public function test_provider_throws_on_uninitialized_access_logging(): void public function test_provider_throws_on_invalid_telemetry_option(): void { - $this->expectException(\AssertionError::class); + $this->expectException(InvariantException::class); $this->expectExceptionMessage('Either "PsrLog" or "Null" is allowed'); InstrumentationHolder::initialize('InvalidOption', new NullLogger()); diff --git a/backend/tests/Unit/SplitFairly/CompensationTest.php b/backend/tests/Unit/SplitFairly/CompensationTest.php index 2cff842..9466bbf 100644 --- a/backend/tests/Unit/SplitFairly/CompensationTest.php +++ b/backend/tests/Unit/SplitFairly/CompensationTest.php @@ -48,6 +48,10 @@ public function test_compensation_when_first_user_spent_more(): void // User1 spent 50, so owes 25. User2 spent 25, so owes 12.5. Difference: 25 - 12.5 = 12.5 self::assertSame(12.5, $compensation->settlement->value); self::assertSame('EUR', $compensation->settlement->currency); + self::assertSame( + sprintf('From: "%s" - to: "%s" - price: "%s"', $compensation->from, $compensation->to, (string) $compensation->settlement), + (string) $compensation + ); } public function test_compensation_when_second_user_spent_more(): void diff --git a/backend/tests/Unit/SplitFairly/EventTest.php b/backend/tests/Unit/SplitFairly/EventTest.php new file mode 100644 index 0000000..10bec04 --- /dev/null +++ b/backend/tests/Unit/SplitFairly/EventTest.php @@ -0,0 +1,51 @@ + 10.0, 'currency' => 'EUR']; + $createdAt = new \DateTimeImmutable('2026-02-12T07:00:00Z'); + + $event = new Event( + createdBy: $createdBy, + subjectType: $subjectType, + subjectId: $subjectId, + eventType: $eventType, + payload: $payload, + createdAt: $createdAt + ); + + self::assertSame($createdBy, $event->createdBy); + self::assertSame($subjectType, $event->subjectType); + self::assertSame($subjectId, $event->subjectId); + self::assertSame($eventType, $event->eventType); + self::assertSame($payload, $event->payload); + self::assertSame($createdAt, $event->createdAt); + } + + public function test_get_value_returns_payload_value(): void + { + $event = new Event( + createdBy: 'user-1', + subjectType: 'Expense', + subjectId: 'exp-1', + eventType: 'tracked', + payload: ['price' => 10.0, 'currency' => 'EUR'], + ); + + self::assertSame(10.0, $event->getValue('price')); + self::assertSame('EUR', $event->getValue('currency')); + } +} diff --git a/backend/tests/Unit/SplitFairly/QueryOptionsTest.php b/backend/tests/Unit/SplitFairly/QueryOptionsTest.php new file mode 100644 index 0000000..8c9f68d --- /dev/null +++ b/backend/tests/Unit/SplitFairly/QueryOptionsTest.php @@ -0,0 +1,43 @@ +createdBy); + self::assertSame($subjectTypes, $options->subjectTypes); + self::assertSame($subjectIds, $options->subjectIds); + self::assertSame($eventTypes, $options->eventTypes); + } + + public function test_is_empty_returns_true_for_empty(): void + { + $options = new QueryOptions(); + self::assertTrue($options->isEmpty()); + } + + public function test_is_empty_returns_false_for_non_empty(): void + { + $options = new QueryOptions(createdBy: ['user-1']); + self::assertFalse($options->isEmpty()); + } +}