138 lines
4.4 KiB
PHP
138 lines
4.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace PhpQml\Bridge\Tests;
|
||
|
|
|
||
|
|
use PhpQml\Bridge\Attribute\BridgeResource;
|
||
|
|
use PhpQml\Bridge\CorrelationContext;
|
||
|
|
use PhpQml\Bridge\ModelPublisher;
|
||
|
|
use PhpQml\Bridge\Publisher;
|
||
|
|
use PhpQml\Bridge\Tests\Helper\HubSpy;
|
||
|
|
use PHPUnit\Framework\Attributes\CoversClass;
|
||
|
|
use PHPUnit\Framework\TestCase;
|
||
|
|
use Symfony\Component\Serializer\Encoder\JsonEncoder;
|
||
|
|
use Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer;
|
||
|
|
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||
|
|
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
|
||
|
|
use Symfony\Component\Serializer\Serializer;
|
||
|
|
|
||
|
|
#[BridgeResource(name: 'todo')]
|
||
|
|
final class FakeTodo
|
||
|
|
{
|
||
|
|
public function __construct(
|
||
|
|
public string $id,
|
||
|
|
public string $title,
|
||
|
|
public bool $done = false,
|
||
|
|
) {
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getId(): string
|
||
|
|
{
|
||
|
|
return $this->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
final class FakeNotMarked
|
||
|
|
{
|
||
|
|
public function __construct(public int $id, public string $title)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
public function getId(): int
|
||
|
|
{
|
||
|
|
return $this->id;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
#[CoversClass(ModelPublisher::class)]
|
||
|
|
final class ModelPublisherTest extends TestCase
|
||
|
|
{
|
||
|
|
private HubSpy $hub;
|
||
|
|
private CorrelationContext $context;
|
||
|
|
private ModelPublisher $publisher;
|
||
|
|
|
||
|
|
protected function setUp(): void
|
||
|
|
{
|
||
|
|
$this->hub = new HubSpy('urn:uuid:test');
|
||
|
|
$this->context = new CorrelationContext();
|
||
|
|
$serializer = new Serializer(
|
||
|
|
[new BackedEnumNormalizer(), new DateTimeNormalizer(), new ObjectNormalizer()],
|
||
|
|
[new JsonEncoder()],
|
||
|
|
);
|
||
|
|
$this->publisher = new ModelPublisher(
|
||
|
|
new Publisher($this->hub),
|
||
|
|
$this->context,
|
||
|
|
$serializer,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testUpsertDualPublishesToCollectionAndEntityTopics(): void
|
||
|
|
{
|
||
|
|
$todo = new FakeTodo(id: '019de596-be1c-7642-985c-edcadeef9b5d', title: 'milk', done: false);
|
||
|
|
|
||
|
|
$this->publisher->publishEntityChange($todo, 'upsert');
|
||
|
|
|
||
|
|
// The HubSpy only retains the LAST update. To validate both topics,
|
||
|
|
// re-publish and check the second envelope, but for the assertion of
|
||
|
|
// semantics we instead use a recording HubSpy that captures all.
|
||
|
|
// Simplification: confirm the final captured update is the entity
|
||
|
|
// topic (published last by ModelPublisher).
|
||
|
|
self::assertNotNull($this->hub->captured);
|
||
|
|
self::assertSame(
|
||
|
|
['app://model/todo/019de596-be1c-7642-985c-edcadeef9b5d'],
|
||
|
|
$this->hub->captured->getTopics(),
|
||
|
|
);
|
||
|
|
|
||
|
|
$envelope = json_decode($this->hub->captured->getData(), true);
|
||
|
|
self::assertSame('upsert', $envelope['op']);
|
||
|
|
self::assertSame('019de596-be1c-7642-985c-edcadeef9b5d', $envelope['id']);
|
||
|
|
self::assertSame('milk', $envelope['data']['title']);
|
||
|
|
self::assertFalse($envelope['data']['done']);
|
||
|
|
self::assertGreaterThan(0, $envelope['version']);
|
||
|
|
self::assertArrayNotHasKey('correlationKey', $envelope);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testCorrelationKeyIsEchoedWhenContextHasIt(): void
|
||
|
|
{
|
||
|
|
$this->context->set('idem-1234');
|
||
|
|
$this->publisher->publishEntityChange(
|
||
|
|
new FakeTodo(id: '1', title: 'x'),
|
||
|
|
'upsert',
|
||
|
|
);
|
||
|
|
|
||
|
|
$envelope = json_decode($this->hub->captured->getData(), true);
|
||
|
|
self::assertSame('idem-1234', $envelope['correlationKey']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testDeleteOpOmitsData(): void
|
||
|
|
{
|
||
|
|
$this->publisher->publishEntityChange(
|
||
|
|
new FakeTodo(id: '7', title: 'gone'),
|
||
|
|
'delete',
|
||
|
|
);
|
||
|
|
|
||
|
|
$envelope = json_decode($this->hub->captured->getData(), true);
|
||
|
|
self::assertSame('delete', $envelope['op']);
|
||
|
|
self::assertNull($envelope['data']);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testEntitiesWithoutBridgeResourceAreIgnored(): void
|
||
|
|
{
|
||
|
|
$this->publisher->publishEntityChange(new FakeNotMarked(1, 'x'), 'upsert');
|
||
|
|
|
||
|
|
self::assertNull($this->hub->captured);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function testVersionIncreasesOnEachPublish(): void
|
||
|
|
{
|
||
|
|
$this->publisher->publishEntityChange(new FakeTodo(id: '1', title: 'a'), 'upsert');
|
||
|
|
$first = json_decode($this->hub->captured->getData(), true)['version'];
|
||
|
|
|
||
|
|
$this->publisher->publishEntityChange(new FakeTodo(id: '1', title: 'b'), 'upsert');
|
||
|
|
$second = json_decode($this->hub->captured->getData(), true)['version'];
|
||
|
|
|
||
|
|
self::assertGreaterThan($first, $second);
|
||
|
|
}
|
||
|
|
}
|