BddAggregateTestHelper::isClassOrSubClass()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 2
crap 1
1
<?php
2
/******************************************************************************
3
 * Copyright (c) 2016 Constantin Galbenu <[email protected]>             *
4
 ******************************************************************************/
5
6
namespace Gica\Cqrs\Testing;
7
8
9
use Gica\CodeAnalysis\Shared\ClassComparison\SubclassComparator;
10
use Gica\Cqrs\Command;
11
use Gica\Cqrs\Command\CommandApplier;
12
use Gica\Cqrs\Command\CommandSubscriber;
13
use Gica\Cqrs\Event;
14
use Gica\Cqrs\Event\EventDispatcher;
15
use Gica\Cqrs\Event\EventDispatcher\EventDispatcherBySubscriber;
16
use Gica\Cqrs\Event\EventsApplier\EventsApplierOnAggregate;
17
use Gica\Cqrs\Event\EventSubscriber\ManualEventSubscriber;
18
use Gica\Cqrs\Event\EventWithMetaData;
19
use Gica\Cqrs\Event\MetaData;
20
use Gica\Cqrs\Testing\Exceptions\ExpectedEventNotYielded;
21
use Gica\Cqrs\Testing\Exceptions\NoExceptionThrown;
22
use Gica\Cqrs\Testing\Exceptions\TooManyEventsFired;
23
use Gica\Cqrs\Testing\Exceptions\WrongEventClassYielded;
24
use Gica\Cqrs\Testing\Exceptions\WrongExceptionClassThrown;
25
use Gica\Cqrs\Testing\Exceptions\WrongExceptionMessageWasThrown;
26
27
class BddAggregateTestHelper
28
{
29
    private $aggregateId;
30
31
    /** @var EventDispatcher */
32
    private $eventDispatcher;
33
34
    private $priorEvents = [];
35
36
    /** @var Command */
37
    private $command;
38
    private $aggregate;
39
40
    /** @var EventsApplierOnAggregate */
41
    private $eventsApplierOnAggregate;
42
43
    /** @var CommandApplier */
44
    private $commandApplier;
45
    /**
46
     * @var CommandSubscriber
47
     */
48
    private $commandSubscriber;
49
50 9
    public function __construct(
51
        CommandSubscriber $commandSubscriber
52
    )
53
    {
54 9
        $this->commandSubscriber = $commandSubscriber;
55 9
        $this->eventDispatcher = new EventDispatcherBySubscriber(new ManualEventSubscriber());
56 9
        $this->eventsApplierOnAggregate = new EventsApplierOnAggregate();
57 9
        $this->commandApplier = new CommandApplier();
58
59 9
        $this->priorEvents = [];
60 9
        $this->command = null;
61 9
    }
62
63 8
    private function getCommandSubscriber(): CommandSubscriber
64
    {
65 8
        return $this->commandSubscriber;
66
    }
67
68 9
    public function onAggregate($aggregate)
69
    {
70 9
        $this->aggregate = $aggregate;
71 9
        $this->aggregateId = 123;
72 9
    }
73
74 9
    public function given(...$priorEvents)
75
    {
76 9
        $this->priorEvents = $this->decorateEventsWithMetadata($priorEvents);
77 9
    }
78
79
    /**
80
     * @param Event[] $priorEvents
81
     * @return EventWithMetaData[]
82
     */
83
    private function decorateEventsWithMetadata(array $priorEvents)
84
    {
85 9
        return array_map(function ($event) {
86 9
            return $this->decorateEventWithMetaData($event);
87 9
        }, $priorEvents);
88
    }
89
90 8
    public function when(Command $command)
91
    {
92 8
        $this->command = $command;
93 8
    }
94
95 4
    public function then(...$expectedEvents)
96
    {
97 4
        $this->checkCommand($this->command);
98
99 4
        $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
100
101 4
        $newEvents = $this->executeCommand($this->command);
102
103 4
        $this->assertTheseEvents($expectedEvents, $newEvents);
104 1
    }
105
106
    /**
107
     * @param Command $command
108
     * @return array
109
     * @throws \Exception
110
     */
111 4
    public function executeCommand(Command $command)
112
    {
113 4
        $handler = $this->getCommandSubscriber()->getHandlerForCommand($command);
114
115 4
        $newEventsGenerator = $this->commandApplier->applyCommand($this->aggregate, $command, $handler->getMethodName());
116
117
        /** @var EventWithMetaData[] $eventsWithMetaData */
118 4
        $eventsWithMetaData = [];
119
120 4
        $newEvents = [];
121
122 4
        foreach ($newEventsGenerator as $event) {
123 4
            $eventWithMetaData = $this->decorateEventWithMetaData($event);
124
125 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, [$eventWithMetaData]);
126
127 4
            $eventsWithMetaData[] = $eventWithMetaData;
128 4
            $newEvents[] = $event;
129
        }
130
131 4
        foreach ($eventsWithMetaData as $eventWithMetaData) {
132 4
            $this->eventDispatcher->dispatchEvent($eventWithMetaData);
133
        }
134
135 4
        return $newEvents;
136
    }
137
138 9
    private function decorateEventWithMetaData($event): EventWithMetaData
139
    {
140 9
        return new EventWithMetaData($event, $this->factoryMetaData());
141
    }
142
143 5
    public function thenShouldFailWith($exceptionClass, $message = null)
144
    {
145 5
        $this->checkCommand($this->command);
146
147
        try {
148 4
            $handler = $this->getCommandSubscriber()->getHandlerForCommand($this->command);
149
150 4
            $this->eventsApplierOnAggregate->applyEventsOnAggregate($this->aggregate, $this->priorEvents);
151
152 4
            iterator_to_array(
153 4
                $this->commandApplier->applyCommand(
154 4
                    $this->aggregate, $this->command, $handler->getMethodName()));
155
156 1
            throw new NoExceptionThrown(
157 1
                sprintf("Exception '%s' was is expected, but none was thrown", $exceptionClass));
158
159 4
        } catch (\Throwable $thrownException) {
160
161 4
            if ($thrownException instanceof NoExceptionThrown) {
162 1
                throw $thrownException;//rethrown
163
            }
164
165 3
            if (!$this->isClassOrSubClass($exceptionClass, $thrownException)) {
166 1
                throw new WrongExceptionClassThrown(
167 1
                    sprintf(
168 1
                        "Exception '%s' was expected, but '%s(%s)' was thrown",
169 1
                        $exceptionClass,
170 1
                        get_class($thrownException),
171 1
                        $thrownException->getMessage()));
172
            }
173
174 2
            if ($message && $thrownException->getMessage() != $message) {
175 1
                throw new WrongExceptionMessageWasThrown(
176 1
                    sprintf(
177 1
                        "Exception with message '%s' was expected, but '%s' was thrown",
178 1
                        $message,
179 1
                        $thrownException->getMessage()));
180
            }
181
        }
182 1
    }
183
184 4
    public function assertTheseEvents(array $expectedEvents, array $actualEvents)
185
    {
186 4
        $expectedEvents = array_values($expectedEvents);
187 4
        $actualEvents = array_values($actualEvents);
188
189 4
        $this->checkForToFewEvents($expectedEvents, $actualEvents);
190 2
        $this->checkForToManyEvents(count($actualEvents) - count($expectedEvents));
191 1
    }
192
193 4
    private function checkForToFewEvents(array $expectedEvents, array $actualEvents)
194
    {
195 4
        foreach ($expectedEvents as $k => $expectedEvent) {
196 4
            if (!isset($actualEvents[$k])) {
197 1
                throw new ExpectedEventNotYielded(
198 1
                    "Expected event no. $k not fired (should have class: " . get_class($expectedEvent) . ")");
199
            }
200
201 4
            $actualEvent = $actualEvents[$k];
202
203 4
            if ($this->hashEvent($expectedEvent) != $this->hashEvent($actualEvent)) {
204 1
                throw new WrongEventClassYielded(
205 4
                    "Wrong event no. {$k} of class " . get_class($expectedEvent) . " emitted");
206
            }
207
        }
208 2
    }
209
210 2
    private function checkForToManyEvents(int $additionalCount)
211
    {
212 2
        if ($additionalCount > 0) {
213 1
            throw new TooManyEventsFired(
214 1
                sprintf("Additional %d events fired", $additionalCount));
215
        }
216 1
    }
217
218 4
    public function hashEvent($event)
219
    {
220 4
        return array_merge(['___class' => get_class($event)], (array)($event));
221
    }
222
223 9
    private function factoryMetaData(): MetaData
224
    {
225 9
        return new MetaData(
226 9
            $this->aggregateId, get_class($this->aggregate), new \DateTimeImmutable(), mt_rand()
227
        );
228
    }
229
230 3
    private function isClassOrSubClass(string $parentClass, $childClass): bool
231
    {
232 3
        return (new SubclassComparator())->isASubClassOrSameClass($childClass, $parentClass);
233
    }
234
235 9
    private function checkCommand($command)
236
    {
237 9
        if (!$command instanceof Command) {
238 1
            throw new \Exception("Command is missing. Have you called method when()?");
239
        }
240
    }
241
}