Skip to content

Commit d3d00a7

Browse files
committed
Add unit tests for domain, infrastructure, and envelope components
1 parent 659c2ee commit d3d00a7

18 files changed

Lines changed: 1895 additions & 0 deletions
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Core\Tests\Integration\Bus;
6+
7+
use OpenSolid\Core\Application\Command\Message\Command;
8+
use OpenSolid\Core\Domain\Envelop\Attribute\Envelope as EnvelopeAttribute;
9+
use OpenSolid\Core\Domain\Envelop\Stamp\TransportStamp;
10+
use OpenSolid\Core\Infrastructure\Bus\Command\Error\NoHandlerForCommand;
11+
use OpenSolid\Core\Infrastructure\Bus\Command\SymfonyCommandBus;
12+
use OpenSolid\Core\Infrastructure\Bus\Envelop\Stamp\Transformer\ChainStampTransformer;
13+
use OpenSolid\Core\Infrastructure\Bus\Envelop\Stamp\Transformer\DefaultStampTransformer;
14+
use PHPUnit\Framework\Attributes\Test;
15+
use PHPUnit\Framework\TestCase;
16+
use Symfony\Component\Messenger\Envelope;
17+
use Symfony\Component\Messenger\Exception\HandlerFailedException;
18+
use Symfony\Component\Messenger\Exception\NoHandlerForMessageException;
19+
use Symfony\Component\Messenger\MessageBusInterface;
20+
use Symfony\Component\Messenger\Stamp\HandledStamp;
21+
use Symfony\Component\Messenger\Stamp\SentStamp;
22+
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
23+
24+
class SymfonyCommandBusTest extends TestCase
25+
{
26+
private ChainStampTransformer $stampTransformer;
27+
28+
protected function setUp(): void
29+
{
30+
$this->stampTransformer = new ChainStampTransformer([
31+
new DefaultStampTransformer(),
32+
]);
33+
}
34+
35+
#[Test]
36+
public function executesCommandAndReturnsResult(): void
37+
{
38+
$command = new TestIntegrationCommand();
39+
$expectedResult = 'command-executed';
40+
41+
$envelope = new Envelope($command, [
42+
new HandledStamp($expectedResult, 'TestHandler::__invoke'),
43+
]);
44+
45+
$messageBus = $this->createMock(MessageBusInterface::class);
46+
$messageBus->expects($this->once())
47+
->method('dispatch')
48+
->willReturn($envelope);
49+
50+
$bus = new SymfonyCommandBus($messageBus, $this->stampTransformer);
51+
$result = $bus->execute($command);
52+
53+
$this->assertSame($expectedResult, $result);
54+
}
55+
56+
#[Test]
57+
public function executesCommandWithStampsFromEnvelopeAttribute(): void
58+
{
59+
$command = new TestAsyncCommand();
60+
61+
$capturedEnvelope = null;
62+
$messageBus = $this->createMock(MessageBusInterface::class);
63+
$messageBus->expects($this->once())
64+
->method('dispatch')
65+
->willReturnCallback(function (Envelope $envelope) use (&$capturedEnvelope) {
66+
$capturedEnvelope = $envelope;
67+
return $envelope->with(
68+
new HandledStamp('result', 'Handler'),
69+
);
70+
});
71+
72+
$bus = new SymfonyCommandBus($messageBus, $this->stampTransformer);
73+
$bus->execute($command);
74+
75+
$this->assertNotNull($capturedEnvelope);
76+
$stamps = $capturedEnvelope->all(TransportNamesStamp::class);
77+
$this->assertCount(1, $stamps);
78+
$this->assertSame(['async'], $stamps[0]->getTransportNames());
79+
}
80+
81+
#[Test]
82+
public function throwsNoHandlerForCommandOnNoHandler(): void
83+
{
84+
$command = new TestIntegrationCommand();
85+
86+
$messageBus = $this->createStub(MessageBusInterface::class);
87+
$messageBus->method('dispatch')
88+
->willThrowException(new NoHandlerForMessageException());
89+
90+
$bus = new SymfonyCommandBus($messageBus, $this->stampTransformer);
91+
92+
$this->expectException(NoHandlerForCommand::class);
93+
94+
$bus->execute($command);
95+
}
96+
97+
#[Test]
98+
public function unwrapsHandlerFailedException(): void
99+
{
100+
$command = new TestIntegrationCommand();
101+
$originalException = new \InvalidArgumentException('Invalid input');
102+
$handlerFailedException = new HandlerFailedException(
103+
new Envelope($command),
104+
[$originalException]
105+
);
106+
107+
$messageBus = $this->createStub(MessageBusInterface::class);
108+
$messageBus->method('dispatch')
109+
->willThrowException($handlerFailedException);
110+
111+
$bus = new SymfonyCommandBus($messageBus, $this->stampTransformer);
112+
113+
$this->expectException(\InvalidArgumentException::class);
114+
$this->expectExceptionMessage('Invalid input');
115+
116+
$bus->execute($command);
117+
}
118+
119+
#[Test]
120+
public function returnsNullForAsyncHandling(): void
121+
{
122+
$command = new TestIntegrationCommand();
123+
124+
$envelope = new Envelope($command, [
125+
new SentStamp('async', 'AsyncSender'),
126+
]);
127+
128+
$messageBus = $this->createStub(MessageBusInterface::class);
129+
$messageBus->method('dispatch')
130+
->willReturn($envelope);
131+
132+
$bus = new SymfonyCommandBus($messageBus, $this->stampTransformer);
133+
$result = $bus->execute($command);
134+
135+
$this->assertNull($result);
136+
}
137+
}
138+
139+
final readonly class TestIntegrationCommand extends Command
140+
{
141+
}
142+
143+
#[EnvelopeAttribute([new TransportStamp('async')])]
144+
final readonly class TestAsyncCommand extends Command
145+
{
146+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace OpenSolid\Core\Tests\Integration\Bus;
6+
7+
use OpenSolid\Core\Domain\Envelop\Attribute\Envelope as EnvelopeAttribute;
8+
use OpenSolid\Core\Domain\Envelop\Stamp\TransportStamp;
9+
use OpenSolid\Core\Domain\Event\Message\DomainEvent;
10+
use OpenSolid\Core\Infrastructure\Bus\Envelop\Stamp\Transformer\ChainStampTransformer;
11+
use OpenSolid\Core\Infrastructure\Bus\Envelop\Stamp\Transformer\DefaultStampTransformer;
12+
use OpenSolid\Core\Infrastructure\Bus\Event\SymfonyEventBus;
13+
use PHPUnit\Framework\Attributes\Test;
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Messenger\Envelope;
16+
use Symfony\Component\Messenger\MessageBusInterface;
17+
use Symfony\Component\Messenger\Stamp\TransportNamesStamp;
18+
19+
class SymfonyEventBusTest extends TestCase
20+
{
21+
private ChainStampTransformer $stampTransformer;
22+
23+
protected function setUp(): void
24+
{
25+
$this->stampTransformer = new ChainStampTransformer([
26+
new DefaultStampTransformer(),
27+
]);
28+
}
29+
30+
#[Test]
31+
public function publishesSingleEvent(): void
32+
{
33+
$event = new TestIntegrationEvent('aggregate-1');
34+
35+
$messageBus = $this->createMock(MessageBusInterface::class);
36+
$messageBus->expects($this->once())
37+
->method('dispatch')
38+
->with($this->isInstanceOf(Envelope::class))
39+
->willReturnCallback(fn (Envelope $e) => $e);
40+
41+
$bus = new SymfonyEventBus($messageBus, $this->stampTransformer);
42+
$bus->publish($event);
43+
}
44+
45+
#[Test]
46+
public function publishesMultipleEvents(): void
47+
{
48+
$event1 = new TestIntegrationEvent('aggregate-1');
49+
$event2 = new TestIntegrationEvent('aggregate-2');
50+
$event3 = new TestIntegrationEvent('aggregate-3');
51+
52+
$messageBus = $this->createMock(MessageBusInterface::class);
53+
$messageBus->expects($this->exactly(3))
54+
->method('dispatch')
55+
->willReturnCallback(fn (Envelope $e) => $e);
56+
57+
$bus = new SymfonyEventBus($messageBus, $this->stampTransformer);
58+
$bus->publish($event1, $event2, $event3);
59+
}
60+
61+
#[Test]
62+
public function publishesNoEventsWhenEmpty(): void
63+
{
64+
$messageBus = $this->createMock(MessageBusInterface::class);
65+
$messageBus->expects($this->never())
66+
->method('dispatch');
67+
68+
$bus = new SymfonyEventBus($messageBus, $this->stampTransformer);
69+
$bus->publish();
70+
}
71+
72+
#[Test]
73+
public function publishesEventWithStampsFromEnvelopeAttribute(): void
74+
{
75+
$event = new TestAsyncEvent('aggregate-1');
76+
77+
$capturedEnvelope = null;
78+
$messageBus = $this->createMock(MessageBusInterface::class);
79+
$messageBus->expects($this->once())
80+
->method('dispatch')
81+
->willReturnCallback(function (Envelope $envelope) use (&$capturedEnvelope) {
82+
$capturedEnvelope = $envelope;
83+
return $envelope;
84+
});
85+
86+
$bus = new SymfonyEventBus($messageBus, $this->stampTransformer);
87+
$bus->publish($event);
88+
89+
$this->assertNotNull($capturedEnvelope);
90+
$stamps = $capturedEnvelope->all(TransportNamesStamp::class);
91+
$this->assertCount(1, $stamps);
92+
$this->assertSame(['async'], $stamps[0]->getTransportNames());
93+
}
94+
95+
#[Test]
96+
public function dispatchesEnvelopeWrappedMessage(): void
97+
{
98+
$event = new TestIntegrationEvent('aggregate-1');
99+
100+
$messageBus = $this->createMock(MessageBusInterface::class);
101+
$messageBus->expects($this->once())
102+
->method('dispatch')
103+
->willReturnCallback(function (Envelope $envelope) use ($event) {
104+
$this->assertSame($event, $envelope->getMessage());
105+
106+
return $envelope;
107+
});
108+
109+
$bus = new SymfonyEventBus($messageBus, $this->stampTransformer);
110+
$bus->publish($event);
111+
}
112+
}
113+
114+
final readonly class TestIntegrationEvent extends DomainEvent
115+
{
116+
}
117+
118+
#[EnvelopeAttribute([new TransportStamp('async')])]
119+
final readonly class TestAsyncEvent extends DomainEvent
120+
{
121+
}

0 commit comments

Comments
 (0)