test: close coverage gaps from the recent rotation + Mercure work
CI / test (push) Has been cancelled
CI / test (push) Has been cancelled
Frontend (90.15→95.37 stmts / 91.83→97.01 lines):
- useDeviceMercure: full composable test suite via a fake EventSource —
open/merge/ignore-stale/parse-error/reconnect/dynamic-add/remove/
no-op-when-unconfigured/cleanup-on-unmount.
- HomeView: cover onTimePart's AM/PM and minute branches plus the
nextPollExpectedAt-null fallback paths in the next-update preview.
Backend (no instrumentation before; pcov was already in the image,
just needed a <coverage> block in phpunit.dist.xml):
- RotationService: one test per mode (NewestUpload, Random,
LeastRecentlyShown), one for never-shown sorting first under LRS,
and two for prioritizeNeverShown — narrows when never-shown exists,
falls through to mode otherwise.
- DeviceSerializer: contract test on the wire shape (REST + Mercure
use the same serializer; silent rename here would break live updates
instantly).
- MercurePublisher: topic format + JSON encoding + the swallow-
exceptions guarantee (a flaky hub must not break poll responses).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Unit\Service;
|
||||
|
||||
use App\Service\MercurePublisher;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
/**
|
||||
* MercurePublisher's contract is small but load-bearing:
|
||||
* 1. The topic format includes the device id (the SPA subscribes by id).
|
||||
* 2. The payload is published as JSON.
|
||||
* 3. A throwing hub MUST NOT propagate — a flaky Mercure container can
|
||||
* never break a poll response or a settings PATCH for the user.
|
||||
*/
|
||||
class MercurePublisherTest extends TestCase
|
||||
{
|
||||
public function test_publishes_to_device_topic_with_json_payload(): void
|
||||
{
|
||||
$captured = null;
|
||||
$hub = $this->createMock(HubInterface::class);
|
||||
$hub->expects($this->once())
|
||||
->method('publish')
|
||||
->willReturnCallback(function (Update $u) use (&$captured) {
|
||||
$captured = $u;
|
||||
return 'urn:uuid:test';
|
||||
});
|
||||
|
||||
$publisher = new MercurePublisher($hub, new NullLogger());
|
||||
$publisher->publishDevice(42, ['id' => 42, 'name' => 'Living Room']);
|
||||
|
||||
$this->assertNotNull($captured);
|
||||
$this->assertSame(
|
||||
['https://pictureframe.edholm.me/devices/42'],
|
||||
$captured->getTopics(),
|
||||
);
|
||||
$this->assertSame('{"id":42,"name":"Living Room"}', $captured->getData());
|
||||
}
|
||||
|
||||
public function test_swallows_hub_exceptions_so_callers_never_blow_up(): void
|
||||
{
|
||||
$hub = $this->createMock(HubInterface::class);
|
||||
$hub->method('publish')->willThrowException(new \RuntimeException('hub down'));
|
||||
|
||||
$publisher = new MercurePublisher($hub, new NullLogger());
|
||||
|
||||
// No exception should escape — if this throws, the test fails.
|
||||
$publisher->publishDevice(42, ['id' => 42]);
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user