Passed
Push — master ( 29eb7e...ba19da )
by Aidyn
19:10 queued 10:28
created

EventSauceTestCase::aggregateRootId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace Chocofamily\LaravelEventSauce;
4
5
use EventSauce\EventSourcing\AggregateRoot;
6
use EventSauce\EventSourcing\AggregateRootId;
7
use EventSauce\EventSourcing\AggregateRootRepository;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEvent...AggregateRootRepository. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
8
use EventSauce\EventSourcing\ConstructingAggregateRootRepository;
9
use EventSauce\EventSourcing\Consumer;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\Consumer. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
10
use EventSauce\EventSourcing\DefaultHeadersDecorator;
11
use EventSauce\EventSourcing\EventStager;
12
use EventSauce\EventSourcing\InMemoryMessageRepository;
13
use EventSauce\EventSourcing\MessageDecorator;
14
use EventSauce\EventSourcing\MessageDecoratorChain;
15
use EventSauce\EventSourcing\MessageDispatcher;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\MessageDispatcher. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
16
use EventSauce\EventSourcing\MessageRepository;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Chocofamily\LaravelEventSauce\MessageRepository. Consider defining an alias.

Let?s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let?s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use EventSauce\EventSourcing\PointInTime;
18
use EventSauce\EventSourcing\SynchronousMessageDispatcher;
19
use EventSauce\EventSourcing\TestUtilities\ConsumerThatSerializesMessages;
20
use EventSauce\EventSourcing\Time\Clock;
21
use EventSauce\EventSourcing\Time\TestClock;
22
use Exception;
23
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
24
use LogicException;
25
26
abstract class EventSauceTestCase extends BaseTestCase
27
{
28
    /**
29
     * @var InMemoryMessageRepository
30
     */
31
    protected $messageRepository;
32
33
    /**
34
     * @var AggregateRootRepository
35
     */
36
    protected $repository;
37
38
    /**
39
     * @var Exception|null
40
     */
41
    private $caughtException;
42
43
    /**
44
     * @var object[]
45
     */
46
    private $expectedEvents = [];
47
48
    /**
49
     * @var Exception|null
50
     */
51
    private $theExpectedException;
52
53
    /**
54
     * @var TestClock
55
     */
56
    private $clock;
57
58
    /**
59
     * @var bool
60
     */
61
    private $assertedScenario = false;
62
63
    /**
64
     * @var AggregateRootId
65
     */
66
    protected $aggregateRootId;
67
68
    protected function setUp(): void
69
    {
70
        parent::setUp();
71
        $this->setUpEventSauce();
72
    }
73
74
    protected function setUpEventSauce(): void
75
    {
76
        $className = $this->aggregateRootClassName();
77
        $this->clock = new TestClock();
78
        $this->aggregateRootId = $this->newAggregateRootId();
79
        $this->messageRepository = new InMemoryMessageRepository();
80
        $dispatcher = $this->messageDispatcher();
81
        $decorator = $this->messageDecorator();
82
        $this->repository = $this->aggregateRootRepository(
83
            $className,
84
            $this->messageRepository,
85
            $dispatcher,
86
            $decorator
87
        );
88
        $this->expectedEvents = [];
89
        $this->assertedScenario = false;
90
        $this->theExpectedException = null;
91
        $this->caughtException = null;
92
    }
93
94
    protected function retrieveAggregateRoot(AggregateRootId $id): object
95
    {
96
        return $this->repository->retrieve($id);
97
    }
98
99
    protected function persistAggregateRoot(AggregateRoot $aggregateRoot): void
100
    {
101
        $this->repository->persist($aggregateRoot);
102
    }
103
104
    /**
105
     * @after
106
     */
107
    protected function assertScenario(): void
108
    {
109
        // @codeCoverageIgnoreStart
110
        if ($this->assertedScenario) {
111
            return;
112
        }
113
        // @codeCoverageIgnoreEnd
114
115
        try {
116
            $this->assertExpectedException($this->theExpectedException, $this->caughtException);
117
            $this->assertLastCommitEqualsEvents(...$this->expectedEvents);
118
            $this->messageRepository->purgeLastCommit();
119
        } finally {
120
            $this->assertedScenario = true;
121
            $this->theExpectedException = null;
122
            $this->caughtException = null;
123
        }
124
    }
125
126
    protected function aggregateRootId(): AggregateRootId
127
    {
128
        return $this->aggregateRootId;
129
    }
130
131
    abstract protected function newAggregateRootId(): AggregateRootId;
132
133
    abstract protected function aggregateRootClassName(): string;
134
135
    /**
136
     * @return $this
137
     */
138
    protected function given(object ...$events)
139
    {
140
        $this->repository->persistEvents($this->aggregateRootId(), count($events), ...$events);
141
        $this->messageRepository->purgeLastCommit();
142
143
        return $this;
144
    }
145
146
    public function on(AggregateRootId $id)
147
    {
148
        return new EventStager($id, $this->messageRepository, $this->repository, $this);
149
    }
150
151
    /**
152
     * @return $this
153
     */
154
    protected function when(...$arguments)
155
    {
156
        try {
157
            if (! method_exists($this, 'handle')) {
158
                throw new LogicException(sprintf('Class %s is missing a ::handle method.', get_class($this)));
159
            }
160
161
            $this->handle(...$arguments);
162
        } catch (Exception $exception) {
163
            $this->caughtException = $exception;
164
        }
165
166
        return $this;
167
    }
168
169
    /**
170
     * @return $this
171
     */
172
    protected function then(object ...$events)
173
    {
174
        $this->expectedEvents = $events;
175
176
        return $this;
177
    }
178
179
    /**
180
     * @return $this
181
     */
182
    public function expectToFail(Exception $expectedException)
183
    {
184
        $this->theExpectedException = $expectedException;
185
186
        return $this;
187
    }
188
189
    /**
190
     * @return $this
191
     */
192
    protected function thenNothingShouldHaveHappened()
193
    {
194
        $this->expectedEvents = [];
195
196
        return $this;
197
    }
198
199
    protected function assertLastCommitEqualsEvents(object ...$events): void
200
    {
201
        if (empty($events)) {
202
            return;
203
        }
204
205
        self::assertEquals($events, $this->messageRepository->lastCommit(), 'Events are not equal.');
206
    }
207
208
    private function assertExpectedException(Exception $expectedException = null, Exception $caughtException = null): void
209
    {
210
        if ($expectedException == $caughtException) {
211
            return;
212
        }
213
214
        if (
215
            null !== $caughtException && (
216
                null === $expectedException ||
217
                get_class($expectedException) !== get_class($caughtException))
218
        ) {
219
            throw $caughtException;
220
        }
221
222
        self::assertEquals([$expectedException], [$caughtException], '>> Exceptions are not equal.');
223
    }
224
225
    protected function pointInTime(): PointInTime
226
    {
227
        return $this->clock->pointInTime();
228
    }
229
230
    protected function clock(): Clock
231
    {
232
        return $this->clock;
233
    }
234
235
    protected function messageDispatcher(): MessageDispatcher
236
    {
237
        return new SynchronousMessageDispatcher(
238
            new ConsumerThatSerializesMessages(),
239
            ...$this->consumers()
240
        );
241
    }
242
243
    private function messageDecorator(): MessageDecorator
244
    {
245
        return new MessageDecoratorChain(
246
            new DefaultHeadersDecorator(),
247
            ...$this->decorators()
248
        );
249
    }
250
251
    /**
252
     * @return Consumer[]
253
     */
254
    protected function consumers(): array
255
    {
256
        return [];
257
    }
258
259
    /**
260
     * @return MessageDecorator[]
261
     */
262
    protected function decorators(): array
263
    {
264
        return [];
265
    }
266
267
    protected function aggregateRootRepository(
268
        string $className,
269
        MessageRepository $repository,
270
        MessageDispatcher $dispatcher,
271
        MessageDecorator $decorator
272
    ): AggregateRootRepository {
273
        return new ConstructingAggregateRootRepository(
274
            $className,
275
            $repository,
276
            $dispatcher,
277
            $decorator
278
        );
279
    }
280
}
281