class TestCollectionRepo(TestCase): def setUp(self): assert_event_handlers_empty() # Setup the repo, and a persistence subscriber. stored_event_repo = PythonObjectsStoredEventRepository() event_store = EventStore(stored_event_repo=stored_event_repo) self.repo = CollectionRepo(event_store=event_store) self.ps = PersistenceSubscriber(event_store=event_store) def tearDown(self): self.ps.close() assert_event_handlers_empty() def test(self): # Check the collection is not in the repo. with self.assertRaises(RepositoryKeyError): _ = self.repo['none'] # Register a new collection. collection_id = register_new_collection().id # Check the collection is in the repo. collection = self.repo[collection_id] self.assertIsInstance(collection, Collection) self.assertEqual(collection.id, collection_id) # Check the collection has zero items. self.assertEqual(len(collection.items), 0) # Add item. item1 = 'item1' collection.add_item(item1) # Check the collection is in the repo. collection = self.repo[collection_id] self.assertIsInstance(collection, Collection) self.assertEqual(collection.id, collection_id) # Check the collection has one item. self.assertEqual(len(collection.items), 1) # Remove item. collection.remove_item(item1) # Check the collection is in the repo. collection = self.repo[collection_id] self.assertIsInstance(collection, Collection) self.assertEqual(collection.id, collection_id) # Check the collection has zero items. self.assertEqual(len(collection.items), 0) # Discard the collection. collection.discard() # Check the collection is not in the repo. with self.assertRaises(RepositoryKeyError): _ = self.repo['none']
class TestPersistenceSubscriber(unittest.TestCase): def setUp(self): # Set up a persistence subscriber with a (mock) event store. self.event_store = mock.Mock(spec=AbstractEventStore) self.persistence_subscriber = PersistenceSubscriber( event_store=self.event_store) def tearDown(self): # Close the persistence subscriber. self.persistence_subscriber.close() def test_published_events_are_appended_to_event_store(self): # Check the event store's append method has NOT been called. self.assertEqual(0, self.event_store.append.call_count) # Publish a (mock) domain event. domain_event = mock.Mock(spec=DomainEvent) publish(domain_event) # Check the append method HAS been called once with the domain event. self.event_store.append.assert_called_once_with(domain_event)
class TestPersistenceSubscriber(unittest.TestCase): def setUp(self): # Set up a persistence subscriber with a (mock) event store. self.event_store = mock.Mock(spec=AbstractEventStore) self.persistence_subscriber = PersistenceSubscriber(event_store=self.event_store) def tearDown(self): # Close the persistence subscriber. self.persistence_subscriber.close() def test_published_events_are_appended_to_event_store(self): # Check the event store's append method has NOT been called. self.assertEqual(0, self.event_store.append.call_count) # Publish a (mock) domain event. domain_event = mock.Mock(spec=DomainEvent) publish(domain_event) # Check the append method HAS been called once with the domain event. self.event_store.append.assert_called_once_with(domain_event)
class TestExampleEntity(unittest.TestCase): def setUp(self): # Setup the persistence subscriber. self.event_store = EventStore(PythonObjectsStoredEventRepository()) self.persistence_subscriber = PersistenceSubscriber( event_store=self.event_store) def tearDown(self): self.persistence_subscriber.close() assert_event_handlers_empty() def test_entity_lifecycle(self): # Check the factory creates an instance. example1 = register_new_example(a=1, b=2) self.assertIsInstance(example1, Example) # Check the properties of the Example class. self.assertEqual(1, example1.a) self.assertEqual(2, example1.b) # Check the properties of the EventSourcedEntity class. self.assertTrue(example1.id) self.assertEqual(1, example1.version) self.assertTrue(example1.created_on) # Check a second instance with the same values is not "equal" to the first. example2 = register_new_example(a=1, b=2) self.assertNotEqual(example1, example2) # Setup the repo. repo = ExampleRepo(self.event_store) # Check the example entities can be retrieved from the example repository. entity1 = repo[example1.id] self.assertIsInstance(entity1, Example) self.assertEqual(1, entity1.a) self.assertEqual(2, entity1.b) entity2 = repo[example2.id] self.assertIsInstance(entity2, Example) self.assertEqual(1, entity2.a) self.assertEqual(2, entity2.b) # Check the entity can be updated. entity1.a = 100 self.assertEqual(100, repo[entity1.id].a) entity1.b = -200 self.assertEqual(-200, repo[entity1.id].b) self.assertEqual(0, entity1.count_heartbeats()) entity1.beat_heart() entity1.beat_heart() entity1.beat_heart() self.assertEqual(3, entity1.count_heartbeats()) self.assertEqual(3, repo[entity1.id].count_heartbeats()) # Check the entity can be discarded. entity1.discard() # Check the repo now raises a KeyError. self.assertRaises(RepositoryKeyError, repo.__getitem__, entity1.id) # Check the entity can't be discarded twice. self.assertRaises(AssertionError, entity1.discard) # Should fail to validate event with wrong entity ID. self.assertRaises( EntityIDConsistencyError, entity2._validate_originator, DomainEvent(entity_id=entity2.id + 'wrong', entity_version=0)) # Should fail to validate event with wrong entity version. self.assertRaises(EntityVersionConsistencyError, entity2._validate_originator, DomainEvent(entity_id=entity2.id, entity_version=0)) # Should validate event with correct entity ID and version. entity2._validate_originator( DomainEvent(entity_id=entity2.id, entity_version=entity2.version)) # Check an entity can be reregistered with the same ID. replacement_event = Example.Created(entity_id=entity1.id, a=11, b=12) # replacement = Example.mutate(event=replacement_event) publish(event=replacement_event) # Check the replacement entity can be retrieved from the example repository. replacement = repo[entity1.id] assert isinstance(replacement, Example) self.assertEqual(replacement.a, 11) self.assertEqual(replacement.b, 12) def test_not_implemented_error(self): # Define an event class. class UnsupportedEvent(DomainEvent): pass # Check we get an error when attempting to mutate on the event. self.assertRaises(NotImplementedError, Example.mutate, Example, UnsupportedEvent('1', '0')) def test_mutableproperty(self): # Check we get an error when called with something other than a function. self.assertRaises(ProgrammingError, mutableproperty, 'not a getter') self.assertRaises(ProgrammingError, mutableproperty, 123) self.assertRaises(ProgrammingError, mutableproperty, None) # Call the decorator with a function. getter = lambda: None p = mutableproperty(getter) # Check we got a property object. self.assertIsInstance(p, property) # Check the property object has both setter and getter functions. self.assertTrue(p.fset) self.assertTrue(p.fget) # Pretend we decorated an object. o = EventSourcedEntity(entity_id='1', entity_version=0, domain_event_id=1) o.__dict__['_<lambda>'] = 'value1' # Call the property's getter function. value = p.fget(o) self.assertEqual(value, 'value1') # Call the property's setter function. p.fset(o, 'value2') # Check the attribute has changed. value = p.fget(o) self.assertEqual(value, 'value2') # Check the property's getter function isn't the getter function we passed in. self.assertNotEqual(p.fget, getter) # Define a class that uses the decorator. class Aaa(EventSourcedEntity): "An event sourced entity." def __init__(self, a, *args, **kwargs): super(Aaa, self).__init__(*args, **kwargs) self._a = a @mutableproperty def a(self): "A mutable event sourced property." # Instantiate the class and check assigning to the property publishes an event and updates the object state. published_events = [] subscription = (lambda x: True, lambda x: published_events.append(x)) subscribe(*subscription) entity_id = '1' try: aaa = Aaa(entity_id=entity_id, entity_version=None, domain_event_id='0', a=1) self.assertEqual(aaa.a, 1) aaa.a = 'value1' self.assertEqual(aaa.a, 'value1') finally: unsubscribe(*subscription) # Check an event was published. self.assertEqual(len(published_events), 1) # Check the published event was an AttributeChanged event, with the expected attribute values. published_event = published_events[0] self.assertIsInstance(published_event, Aaa.AttributeChanged) self.assertEqual(published_event.name, '_a') self.assertEqual(published_event.value, 'value1') self.assertTrue(published_event.domain_event_id) self.assertEqual(published_event.entity_id, entity_id) def test_static_mutator_method(self): self.assertRaises(NotImplementedError, EventSourcedEntity._mutator, 1, 2) def test_created_mutator_error(self): self.assertRaises(CreatedMutatorRequiresTypeNotInstance, created_mutator, mock.Mock(spec=DomainEvent), 'not a class')
class TestExampleEntity(unittest.TestCase): def setUp(self): # Setup the persistence subscriber. self.event_store = EventStore(PythonObjectsStoredEventRepository()) self.persistence_subscriber = PersistenceSubscriber(event_store=self.event_store) def tearDown(self): self.persistence_subscriber.close() assert_event_handlers_empty() def test_entity_lifecycle(self): # Check the factory creates an instance. example1 = register_new_example(a=1, b=2) self.assertIsInstance(example1, Example) # Check the properties of the Example class. self.assertEqual(1, example1.a) self.assertEqual(2, example1.b) # Check the properties of the EventSourcedEntity class. self.assertTrue(example1.id) self.assertEqual(1, example1.version) self.assertTrue(example1.created_on) # Check a second instance with the same values is not "equal" to the first. example2 = register_new_example(a=1, b=2) self.assertNotEqual(example1, example2) # Setup the repo. repo = ExampleRepo(self.event_store) # Check the example entities can be retrieved from the example repository. entity1 = repo[example1.id] self.assertIsInstance(entity1, Example) self.assertEqual(1, entity1.a) self.assertEqual(2, entity1.b) entity2 = repo[example2.id] self.assertIsInstance(entity2, Example) self.assertEqual(1, entity2.a) self.assertEqual(2, entity2.b) # Check the entity can be updated. entity1.a = 100 self.assertEqual(100, repo[entity1.id].a) entity1.b = -200 self.assertEqual(-200, repo[entity1.id].b) self.assertEqual(0, entity1.count_heartbeats()) entity1.beat_heart() entity1.beat_heart() entity1.beat_heart() self.assertEqual(3, entity1.count_heartbeats()) self.assertEqual(3, repo[entity1.id].count_heartbeats()) # Check the entity can be discarded. entity1.discard() # Check the repo now raises a KeyError. self.assertRaises(RepositoryKeyError, repo.__getitem__, entity1.id) # Check the entity can't be discarded twice. self.assertRaises(AssertionError, entity1.discard) # Should fail to validate event with wrong entity ID. self.assertRaises(EntityIDConsistencyError, entity2._validate_originator, DomainEvent(entity_id=entity2.id+'wrong', entity_version=0) ) # Should fail to validate event with wrong entity version. self.assertRaises(EntityVersionConsistencyError, entity2._validate_originator, DomainEvent(entity_id=entity2.id, entity_version=0) ) # Should validate event with correct entity ID and version. entity2._validate_originator( DomainEvent(entity_id=entity2.id, entity_version=entity2.version) ) # Check an entity can be reregistered with the same ID. replacement_event = Example.Created(entity_id=entity1.id, a=11, b=12) # replacement = Example.mutate(event=replacement_event) publish(event=replacement_event) # Check the replacement entity can be retrieved from the example repository. replacement = repo[entity1.id] assert isinstance(replacement, Example) self.assertEqual(replacement.a, 11) self.assertEqual(replacement.b, 12) def test_not_implemented_error(self): # Define an event class. class UnsupportedEvent(DomainEvent): pass # Check we get an error when attempting to mutate on the event. self.assertRaises(NotImplementedError, Example.mutate, Example, UnsupportedEvent('1', '0')) def test_mutableproperty(self): # Check we get an error when called with something other than a function. self.assertRaises(ProgrammingError, mutableproperty, 'not a getter') self.assertRaises(ProgrammingError, mutableproperty, 123) self.assertRaises(ProgrammingError, mutableproperty, None) # Call the decorator with a function. getter = lambda: None p = mutableproperty(getter) # Check we got a property object. self.assertIsInstance(p, property) # Check the property object has both setter and getter functions. self.assertTrue(p.fset) self.assertTrue(p.fget) # Pretend we decorated an object. o = EventSourcedEntity(entity_id='1', entity_version=0, domain_event_id=1) o.__dict__['_<lambda>'] = 'value1' # Call the property's getter function. value = p.fget(o) self.assertEqual(value, 'value1') # Call the property's setter function. p.fset(o, 'value2') # Check the attribute has changed. value = p.fget(o) self.assertEqual(value, 'value2') # Check the property's getter function isn't the getter function we passed in. self.assertNotEqual(p.fget, getter) # Define a class that uses the decorator. class Aaa(EventSourcedEntity): "An event sourced entity." def __init__(self, a, *args, **kwargs): super(Aaa, self).__init__(*args, **kwargs) self._a = a @mutableproperty def a(self): "A mutable event sourced property." # Instantiate the class and check assigning to the property publishes an event and updates the object state. published_events = [] subscription = (lambda x: True, lambda x: published_events.append(x)) subscribe(*subscription) entity_id = '1' try: aaa = Aaa(entity_id=entity_id, entity_version=None, domain_event_id='0', a=1) self.assertEqual(aaa.a, 1) aaa.a = 'value1' self.assertEqual(aaa.a, 'value1') finally: unsubscribe(*subscription) # Check an event was published. self.assertEqual(len(published_events), 1) # Check the published event was an AttributeChanged event, with the expected attribute values. published_event = published_events[0] self.assertIsInstance(published_event, Aaa.AttributeChanged) self.assertEqual(published_event.name, '_a') self.assertEqual(published_event.value, 'value1') self.assertTrue(published_event.domain_event_id) self.assertEqual(published_event.entity_id, entity_id) def test_static_mutator_method(self): self.assertRaises(NotImplementedError, EventSourcedEntity._mutator, 1, 2) def test_created_mutator_error(self): self.assertRaises(CreatedMutatorRequiresTypeNotInstance, created_mutator, mock.Mock(spec=DomainEvent), 'not a class')
class LogTestCase(AbstractTestCase): @property def stored_event_repo(self): """ Returns a stored event repository. Concrete log test cases will provide this method. """ raise NotImplementedError def setUp(self): super(LogTestCase, self).setUp() # Check we're starting clean, event handler-wise. assert_event_handlers_empty() # Setup the persistence subscriber. self.event_store = EventStore(self.stored_event_repo) self.persistence_subscriber = PersistenceSubscriber(event_store=self.event_store) self.log_repo = LogRepo(self.event_store) def tearDown(self): # Close the persistence subscriber. self.persistence_subscriber.close() super(LogTestCase, self).tearDown() # Check we finished clean, event handler-wise. assert_event_handlers_empty() def test_entity_lifecycle(self): log = self.log_repo.get_or_create(log_name='log1', bucket_size='year') self.assertIsInstance(log, Log) self.assertEqual(log.name, 'log1') self.assertEqual(log.bucket_size, 'year') # Test get_logger and get_log_reader(). logger = get_logger(log) self.assertIsInstance(logger, Logger) message1 = 'This is message 1' message2 = 'This is message 2' message3 = 'This is message 3' message4 = 'This is message 4' message5 = 'This is message 5' message6 = 'This is message 6' event1 = logger.info(message1) event2 = logger.info(message2) event3 = logger.info(message3) halfway = uuid1().hex event4 = logger.info(message4) event5 = logger.info(message5) event6 = logger.info(message6) # Check we can get all the messages (query running in descending order). log_reader = get_log_reader(log, event_store=self.event_store) messages = list(log_reader.get_messages(is_ascending=False)) self.assertEqual(len(messages), 6) self.assertEqual(messages[0], message6) self.assertEqual(messages[1], message5) self.assertEqual(messages[2], message4) self.assertEqual(messages[3], message3) self.assertEqual(messages[4], message2) self.assertEqual(messages[5], message1) # Check we can get all the messages (query running in ascending order). messages = list(log_reader.get_messages(is_ascending=True)) self.assertEqual(len(messages), 6) self.assertEqual(messages[0], message1) self.assertEqual(messages[1], message2) self.assertEqual(messages[2], message3) self.assertEqual(messages[3], message4) self.assertEqual(messages[4], message5) self.assertEqual(messages[5], message6) # Check we can get messages after halfway (query running in descending order). messages = list(log_reader.get_messages(after=halfway, is_ascending=False)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message6) self.assertEqual(messages[1], message5) self.assertEqual(messages[2], message4) # Check we can get messages until halfway (query running in descending order). messages = list(log_reader.get_messages(until=halfway, is_ascending=False)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message3) self.assertEqual(messages[1], message2) self.assertEqual(messages[2], message1) # Check we can get messages until halfway (query running in ascending order). messages = list(log_reader.get_messages(until=halfway, is_ascending=True)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message1) self.assertEqual(messages[1], message2) self.assertEqual(messages[2], message3) # Check we can get messages after halfway (query running in ascending order). messages = list(log_reader.get_messages(after=halfway, is_ascending=True)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message4) self.assertEqual(messages[1], message5) self.assertEqual(messages[2], message6) # Check we can get last three messages (query running in descending order). messages = list(log_reader.get_messages(limit=3, is_ascending=False)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message6) self.assertEqual(messages[1], message5) self.assertEqual(messages[2], message4) # Check we can get first three messages (query running in ascending order). messages = list(log_reader.get_messages(limit=3, is_ascending=True)) self.assertEqual(len(messages), 3) self.assertEqual(messages[0], message1) self.assertEqual(messages[1], message2) self.assertEqual(messages[2], message3) # Check we can get last line (query running in descending order). messages = list(log_reader.get_messages(limit=1, after=halfway, is_ascending=False)) self.assertEqual(len(messages), 1) self.assertEqual(messages[0], message6) # Check we can get the first line after halfway (query running in ascending order). messages = list(log_reader.get_messages(limit=1, after=halfway, is_ascending=True)) self.assertEqual(len(messages), 1) self.assertEqual(messages[0], message4) # Check we can get the first line before halfway (query running in descending order). messages = list(log_reader.get_messages(limit=1, until=halfway, is_ascending=False)) self.assertEqual(len(messages), 1) self.assertEqual(messages[0], message3) # Check we can get the first line (query running in ascending order). messages = list(log_reader.get_messages(limit=1, until=halfway, is_ascending=True)) self.assertEqual(len(messages), 1) self.assertEqual(messages[0], message1) # Check there isn't a line after the last line (query running in ascending order). messages = list(log_reader.get_messages(limit=1, after=event6.domain_event_id, is_ascending=True)) self.assertEqual(len(messages), 0) # Check there is nothing somehow both after and until halfway. messages = list(log_reader.get_messages(after=halfway, until=halfway)) self.assertEqual(len(messages), 0) def test_buckets(self): # Start new log. log = start_new_log(name='log1', bucket_size='second') # Write messages across the time interval start = datetime.datetime.now() logger = get_logger(log) number_of_messages = 300 events = [] for i in range(number_of_messages): message_logged = logger.info(str(i)) events.append(message_logged) sleep(0.01) self.assertGreater(datetime.datetime.now() - start, datetime.timedelta(seconds=1)) # Get the messages in descending order. reader = get_log_reader(log, self.event_store) messages = list(reader.get_messages(is_ascending=False, page_size=10)) self.assertEqual(len(messages), number_of_messages) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, list(reversed([str(i) for i in range(number_of_messages)]))) # Get the messages in ascending order. messages = list(reader.get_messages(is_ascending=True, page_size=10)) self.assertEqual(len(messages), number_of_messages) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(number_of_messages)]) # Get a limited number of messages in descending order. limit = 150 messages = list(reader.get_messages(is_ascending=False, page_size=10, limit=limit)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, list(reversed([str(i) for i in range(number_of_messages)]))[:limit]) # Get a limited number of messages in ascending order. limit = 150 messages = list(reader.get_messages(is_ascending=True, page_size=10, limit=limit)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(limit)]) # Get a limited number of messages in descending order from the midpoint down. limit = 110 midpoint = events[150].domain_event_id messages = list(reader.get_messages(is_ascending=False, page_size=10, limit=limit, until=midpoint)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, list(reversed([str(i) for i in range(150 - limit, 150)]))) # Get a limited number of messages in ascending order from the midpoint up. limit = 110 midpoint = events[149].domain_event_id messages = list(reader.get_messages(is_ascending=True, page_size=10, limit=limit, after=midpoint)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(150, 150 + limit)]) # Get a limited number of messages in descending order above the midpoint down. limit = 200 midpoint = events[150].domain_event_id messages = list(reader.get_messages(is_ascending=False, page_size=10, limit=limit, after=midpoint)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), 150) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, list(reversed([str(i) for i in range(150, 300)]))) # Get a limited number of messages in ascending order below the midpoint up. limit = 200 midpoint = events[149].domain_event_id messages = list(reader.get_messages(is_ascending=True, page_size=10, limit=limit, until=midpoint)) self.assertLess(limit, number_of_messages) self.assertEqual(len(messages), 150) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(150)]) # # Use the last position to start part way through. limit = 20 last_position = reader.position messages = reader.get_messages(is_ascending=True, page_size=10, limit=limit, after=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(150, 150 + limit)]) # Do it again. last_position = reader.position messages = reader.get_messages(is_ascending=True, page_size=10, limit=limit, after=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the same as the created order. self.assertEqual(messages, [str(i) for i in range(150 + limit, 150 + limit * 2)]) # Go back. last_position = reader.position messages = reader.get_messages(is_ascending=False, page_size=10, limit=limit, until=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, [str(i) for i in range(148 + limit * 2, 148 + limit, -1)]) # Go back. last_position = reader.position messages = reader.get_messages(is_ascending=False, page_size=10, limit=limit, until=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, [str(i) for i in range(128 + limit * 2, 128 + limit, -1)]) # Go back. last_position = reader.position messages = reader.get_messages(is_ascending=False, page_size=10, limit=limit, until=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, [str(i) for i in range(108 + limit * 2, 108 + limit, -1)]) # Repeat. messages = reader.get_messages(is_ascending=False, page_size=10, limit=limit, until=last_position) messages = list(messages) self.assertEqual(len(messages), limit) # Expect the order of the messages is the reverse of the created order. self.assertEqual(messages, [str(i) for i in range(108 + limit * 2, 108 + limit, -1)])