Skip to content

Commit 0bdf0c8

Browse files
committed
Add failing test for verify_hash.g
1 parent fde33b0 commit 0bdf0c8

File tree

2 files changed

+44
-15
lines changed

2 files changed

+44
-15
lines changed

eventsourcingdb/event/event.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from dataclasses import dataclass
1+
from dataclasses import dataclass, field
22
from datetime import datetime
33
import json
44
from hashlib import sha256
@@ -10,7 +10,7 @@
1010
Self = TypeVar("Self", bound="Event")
1111

1212

13-
@dataclass(frozen=True)
13+
@dataclass
1414
class Event:
1515
data: dict
1616
source: str
@@ -19,7 +19,7 @@ class Event:
1919
spec_version: str
2020
event_id: str
2121
time: datetime
22-
time_str: str
22+
_time_from_server: str = field(init=False, repr=False)
2323
data_content_type: str
2424
predecessor_hash: str
2525
hash: str
@@ -48,10 +48,10 @@ def parse(unknown_object: dict) -> "Event":
4848
if not isinstance(event_id, str):
4949
raise ValidationError(f"Failed to parse event_id '{event_id}' to string.")
5050

51-
time_str = unknown_object.get("time")
52-
if not isinstance(time_str, str):
53-
raise ValidationError(f"Failed to parse time '{time_str}' to string.")
54-
time = Event.__parse_time(time_str)
51+
time_from_server = unknown_object.get("time")
52+
if not isinstance(time_from_server, str):
53+
raise ValidationError(f"Failed to parse time '{time_from_server}' to string.")
54+
time = Event.__parse_time(time_from_server)
5555

5656
data_content_type = unknown_object.get("datacontenttype")
5757
if not isinstance(data_content_type, str):
@@ -82,28 +82,30 @@ def parse(unknown_object: dict) -> "Event":
8282
if not isinstance(data, dict):
8383
raise ValidationError(f"Failed to parse data '{data}' to object.")
8484

85-
return Event(
85+
event = Event(
8686
data=data,
8787
source=source,
8888
subject=subject,
8989
type=event_type,
9090
spec_version=spec_version,
9191
event_id=event_id,
9292
time=time,
93-
time_str=time_str,
9493
data_content_type=data_content_type,
9594
predecessor_hash=predecessor_hash,
9695
hash=hash,
9796
trace_parent=trace_parent,
9897
trace_state=trace_state,
9998
)
99+
event._time_from_server = time_from_server
100+
101+
return event
100102

101103
def verify_hash(self) -> None:
102104
metadata = "|".join([
103105
self.spec_version,
104106
self.event_id,
105107
self.predecessor_hash,
106-
self.time_str,
108+
self._time_from_server,
107109
self.source,
108110
self.subject,
109111
self.type,
@@ -151,17 +153,17 @@ def to_json(self) -> dict[str, Any]:
151153
return json
152154

153155
@staticmethod
154-
def __parse_time(time_str: str) -> datetime:
155-
if not isinstance(time_str, str):
156-
raise ValidationError(f"Failed to parse time '{time_str}' to datetime.")
156+
def __parse_time(time_from_server: str) -> datetime:
157+
if not isinstance(time_from_server, str):
158+
raise ValidationError(f"Failed to parse time '{time_from_server}' to datetime.")
157159

158-
rest, sub_seconds = time_str.split(".")
160+
rest, sub_seconds = time_from_server.split(".")
159161
sub_seconds = f"{sub_seconds[:6]:06}"
160162
try:
161163
return datetime.fromisoformat(f"{rest}.{sub_seconds}")
162164
except ValueError as value_error:
163165
raise ValidationError(
164-
f"Failed to parse time '{time_str}' to datetime."
166+
f"Failed to parse time '{time_from_server}' to datetime."
165167
) from value_error
166168
except Exception as other_error:
167169
raise InternalError(str(other_error)) from other_error

tests/event/test_verify_hash.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from eventsourcingdb import EventCandidate
4+
from hashlib import sha256
45

56
from ..conftest import TestData
67
from ..shared.database import Database
@@ -27,3 +28,29 @@ async def test_verifies_the_event_hash(
2728

2829
written_event = written_events[0]
2930
written_event.verify_hash()
31+
32+
@staticmethod
33+
@pytest.mark.asyncio
34+
async def test_fails_if_the_event_hash_is_invalid(
35+
database: Database,
36+
test_data: TestData,
37+
) -> None:
38+
client = database.get_client()
39+
40+
written_events = await client.write_events(
41+
[
42+
EventCandidate(
43+
source=test_data.TEST_SOURCE_STRING, subject="/test", type="io.eventsourcingdb.test", data={"value": 23}
44+
)
45+
],
46+
)
47+
48+
assert len(written_events) == 1
49+
50+
written_event = written_events[0]
51+
52+
invalid_hash = sha256("invalid hash").hexdigest()
53+
written_event.hash = invalid_hash
54+
55+
with pytest.raises(ValidationError):
56+
written_event.verify_hash()

0 commit comments

Comments
 (0)