Skip to content
This repository was archived by the owner on Sep 6, 2025. It is now read-only.

Commit 1c32582

Browse files
Ensure redis saves with microseconds and added tests
1 parent 3bb0483 commit 1c32582

File tree

3 files changed

+217
-7
lines changed

3 files changed

+217
-7
lines changed

src/EventStore/Snapshot/Adapter/RedisSnapshotAdapter.php

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public function byId(AggregateIdInterface $id)
4343
'json'
4444
);
4545

46-
if (null === $metadata) {
46+
if (!is_array($metadata)) {
4747
return null;
4848
}
4949

@@ -54,11 +54,14 @@ public function byId(AggregateIdInterface $id)
5454
'json'
5555
);
5656

57+
$createdAt = \DateTimeImmutable::createFromFormat('U.u', $metadata['created_at']);
58+
$createdAt->setTimezone(new \DateTimeZone('UTC'));
59+
5760
return new Snapshot(
5861
$aggregate->getAggregateRootId(),
5962
$aggregate,
6063
$metadata['version'],
61-
new \DateTimeImmutable("@" . $metadata['created_at'])
64+
$createdAt
6265
);
6366
}
6467

@@ -69,7 +72,7 @@ public function save(Snapshot $snapshot)
6972
{
7073
$data = [
7174
'version' => $snapshot->getVersion(),
72-
'created_at' => $snapshot->getCreatedAt()->getTimestamp(),
75+
'created_at' => $snapshot->getCreatedAt()->format('U.u'),
7376
'snapshot' => [
7477
'type' => $snapshot->getType(),
7578
'payload' => $this->serializer->serialize($snapshot->getAggregate(), 'json')
@@ -89,14 +92,12 @@ public function save(Snapshot $snapshot)
8992
*/
9093
public function has(AggregateIdInterface $id, $version)
9194
{
92-
$data = $this->redis->hget(static::KEY_NAMESPACE, (string)$id);
93-
94-
if (!$data) {
95+
if (!$this->redis->hexists(static::KEY_NAMESPACE, (string)$id)) {
9596
return false;
9697
}
9798

9899
$snapshot = $this->serializer->deserialize(
99-
$data,
100+
$this->redis->hget(static::KEY_NAMESPACE, (string)$id),
100101
'array',
101102
'json'
102103
);
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
<?php
2+
3+
namespace HelloFresh\Tests\Engine\EventStore\Snapshot\Adapter;
4+
5+
use HelloFresh\Engine\Domain\AggregateId;
6+
use HelloFresh\Engine\EventStore\Snapshot\Adapter\RedisSnapshotAdapter;
7+
use HelloFresh\Engine\EventStore\Snapshot\Snapshot;
8+
use HelloFresh\Engine\Serializer\SerializerInterface;
9+
use HelloFresh\Tests\Engine\Mock\AggregateRoot;
10+
use HelloFresh\Tests\Engine\Mock\PredisClient;
11+
use PHPUnit\Framework\TestCase;
12+
use Predis\ClientInterface;
13+
14+
class RedisSnapshotAdapterTest extends TestCase
15+
{
16+
/**
17+
* @var ClientInterface|\Prophecy\Prophecy\ObjectProphecy
18+
*/
19+
private $client;
20+
/**
21+
* @var SerializerInterface|\Prophecy\Prophecy\ObjectProphecy
22+
*/
23+
private $serializer;
24+
25+
protected function setUp()
26+
{
27+
$this->client = $this->prophesize(PredisClient::class);
28+
$this->serializer = $this->prophesize(SerializerInterface::class);
29+
}
30+
31+
/**
32+
* @test
33+
*/
34+
public function itCanSaveASnapshot()
35+
{
36+
$id = AggregateId::generate();
37+
$aggregate = AggregateRoot::create($id, 'test');
38+
39+
$snapshot = Snapshot::take($id, $aggregate, '10');
40+
41+
$expectedSerializedAggregate = sprintf('["serialized": "%s"]', spl_object_hash($snapshot));
42+
$expectedStorageArray = [
43+
'version' => '10',
44+
'created_at' => $snapshot->getCreatedAt()->format('U.u'),
45+
'snapshot' => [
46+
'type' => AggregateRoot::class,
47+
'payload' => $expectedSerializedAggregate,
48+
]
49+
];
50+
$expectedStoredData = '["version etc..."]';
51+
52+
$this->serializer->serialize($aggregate, 'json')
53+
->willReturn($expectedSerializedAggregate)
54+
->shouldBeCalledTimes(1);
55+
$this->serializer->serialize($expectedStorageArray, 'json')
56+
->willReturn($expectedStoredData)
57+
->shouldBeCalledTimes(1);
58+
59+
$this->client->hset(RedisSnapshotAdapter::KEY_NAMESPACE, (string)$id, $expectedStoredData)
60+
->shouldBeCalledTimes(1);
61+
62+
$adapter = $this->createAdapter();
63+
$adapter->save($snapshot);
64+
}
65+
66+
/**
67+
* @test
68+
*/
69+
public function aSnapshotCanBeRetrievedById()
70+
{
71+
$id = AggregateId::generate();
72+
73+
$expectedAggregate = AggregateRoot::create($id, 'testing');
74+
75+
$snapshotMetadata = [
76+
'version' => '15',
77+
'created_at' => '1468847497.332610',
78+
'snapshot' => [
79+
'type' => AggregateRoot::class,
80+
'payload' => 'aggregate_data',
81+
]
82+
];
83+
84+
$this->mockRedisHasAndGetData($id, $snapshotMetadata);
85+
86+
$this->serializer->deserialize('aggregate_data', AggregateRoot::class, 'json')
87+
->willReturn($expectedAggregate);
88+
89+
$adapter = $this->createAdapter();
90+
$result = $adapter->byId($id);
91+
92+
$this->assertInstanceOf(Snapshot::class, $result);
93+
$this->assertSame($id, $result->getAggregateId());
94+
$this->assertSame($expectedAggregate, $result->getAggregate());
95+
$this->assertSame('15', $result->getVersion());
96+
$this->assertSame('1468847497.332610', $result->getCreatedAt()->format('U.u'));
97+
$this->assertEquals(new \DateTimeZone('UTC'), $result->getCreatedAt()->getTimezone());
98+
}
99+
100+
/**
101+
* @test
102+
*/
103+
public function aSnapshotCanNotBeRetrievedWhenTheIdIsUnknown()
104+
{
105+
$id = AggregateId::generate();
106+
107+
$this->client->hexists(RedisSnapshotAdapter::KEY_NAMESPACE, (string)$id)
108+
->willReturn(false)
109+
->shouldBeCalledTimes(1);
110+
111+
$adapter = $this->createAdapter();
112+
$result = $adapter->byId($id);
113+
114+
$this->assertNull($result);
115+
}
116+
117+
/**
118+
* @test
119+
*/
120+
public function itIndicatedIfASnapshotOfAggregateWithVersionExists()
121+
{
122+
$id = AggregateId::generate();
123+
$expectedDeserializedRedisData = ['version' => 20];
124+
125+
$this->mockRedisHasAndGetData($id, $expectedDeserializedRedisData);
126+
127+
$adapter = $this->createAdapter();
128+
$result = $adapter->has($id, 20);
129+
130+
$this->assertTrue($result);
131+
}
132+
133+
/**
134+
* @test
135+
*/
136+
public function itIndicatedThatASnapshotOfAggregateIsUnknown()
137+
{
138+
$id = AggregateId::generate();
139+
140+
$this->client->hexists(RedisSnapshotAdapter::KEY_NAMESPACE, (string)$id)
141+
->willReturn(false)
142+
->shouldBeCalledTimes(1);
143+
144+
$adapter = $this->createAdapter();
145+
$result = $adapter->has($id, 15);
146+
147+
$this->assertFalse($result);
148+
}
149+
150+
/**
151+
* @test
152+
*/
153+
public function itIndicatedThatASnapshotOfAggregateIsUnknownWhenTheVersionIsIncorrect()
154+
{
155+
$id = AggregateId::generate();
156+
157+
$this->mockRedisHasAndGetData($id, 20);
158+
159+
$adapter = $this->createAdapter();
160+
$result = $adapter->has($id, 15);
161+
162+
$this->assertFalse($result);
163+
}
164+
165+
166+
/**
167+
* @return RedisSnapshotAdapter
168+
*/
169+
protected function createAdapter()
170+
{
171+
$adapter = new RedisSnapshotAdapter($this->client->reveal(), $this->serializer->reveal());
172+
return $adapter;
173+
}
174+
175+
/**
176+
* @param $id
177+
* @param $expectedDeserializedRedisData
178+
*/
179+
protected function mockRedisHasAndGetData($id, $expectedDeserializedRedisData)
180+
{
181+
$this->client->hexists(RedisSnapshotAdapter::KEY_NAMESPACE, (string)$id)
182+
->willReturn(true)
183+
->shouldBeCalledTimes(1);
184+
185+
$this->client->hget(RedisSnapshotAdapter::KEY_NAMESPACE, (string)$id)
186+
->willReturn('redis_data')
187+
->shouldBeCalledTimes(1);
188+
189+
$this->serializer->deserialize('redis_data', 'array', 'json')
190+
->willReturn($expectedDeserializedRedisData);
191+
}
192+
}

tests/Mock/PredisClient.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
namespace HelloFresh\Tests\Engine\Mock;
4+
5+
use Predis\ClientInterface;
6+
7+
/**
8+
* A custom predis client mock with some of the magic methods added so prophecy can play nice and not mice.
9+
*/
10+
abstract class PredisClient implements ClientInterface
11+
{
12+
abstract public function hset($key, $field, $value);
13+
14+
abstract public function hexists($key, $field);
15+
16+
abstract public function hget($key, $field);
17+
}

0 commit comments

Comments
 (0)