1
\$\begingroup\$

Below is some code that implements an 'event log' whose purpose is to record events and periodically check that all events were recorded in chronological order. The collection of events must be persisted in either plain text or encrypted text.

I tried following TDD so I would like feedback on the tests, the final design (is it ports-and-adaptors?) and how it can be improved.

Thanks.

import datetime
import unittest

import mock
from cryptography.fernet import Fernet
from hamcrest import *


class EventLog(object):

    def __init__(self, event_collection):
        self._event_collection = event_collection

    def record_event(self, event):
        self._event_collection.add(event)

    def check_consistency(self):
        sorted_collection = self._event_collection.sort()

        if not sorted_collection.equal_to(self._event_collection):
            raise ValueError


class EventCollection(object):
    def __init__(self, iterable=None):
        if iterable is None:
            self._collection = []
        else:
            assert isinstance(iterable, list)
            self._collection = iterable

    def __iter__(self):
        return iter(self._collection)

    def __len__(self):
        return len(self._collection)

    def add(self, event):
        self._collection.append(event)

    def sort(self):
        return EventCollection(sorted(self._collection))

    def equal_to(self, other):
        comparisons = []
        if len(self) != len(other):
            return False

        for a, b in zip(self, other):
            comparisons.append(a == b)
        return all(comparisons)

    def to_file(self, file_obj):
        file_obj.write(self._collection)

    @staticmethod
    def from_file(file_obj):
        text = file_obj.read()
        collection = text.strip('[]').split(',')
        collection = [Event(datetime.datetime.strptime(item.strip(' '), '%Y-%m-%d %H:%M:%S')) for item in collection]
        return EventCollection(collection)


class Event(object):
    def __init__(self, obj):
        self._obj = obj

    def __eq__(self, other):
        return self._obj == other._obj

    def __le__(self, other):
        return self._obj <= other._obj

    def __lt__(self, other):
        return self._obj < other._obj

    def __gt__(self, other):
        return self._obj > other._obj

    def __ge__(self, other):
        return self._obj >= other._obj

    def __repr__(self):
        return str(self._obj)


class EventLogUsesEventCollectionAsExpectedWhenCheckingForConsistency(unittest.TestCase):

    def setUp(self):
        self.event_collection = mock.Mock()  # EventCollection()
        self.event_log = EventLog(self.event_collection)

        self.event = mock.Mock()  # Event()
        self.event_log.record_event(self.event)

    def test_what_is_this_testing(self):
        self.event_log.check_consistency()

        self.event_collection.add.assert_called_once_with(self.event)
        self.event_collection.sort.assert_called_once_with()


class EventLogWithInconsistentEventSequenceTests(unittest.TestCase):

    def setUp(self):
        event_collection = EventCollection()
        self.event_log = EventLog(event_collection)

        # Events are not recorded in order
        self.event_log.record_event(Event(datetime.datetime(2016, 7, 11)))
        self.event_log.record_event(Event(datetime.datetime(2016, 7, 10)))

    def test_raises_value_error(self):
        with self.assertRaises(ValueError):
            self.event_log.check_consistency()


class EventLogWithConsistentEventSequenceTests(unittest.TestCase):

    def setUp(self):
        event_collection = EventCollection()
        self.event_log = EventLog(event_collection)

        self.event_log.record_event(Event(datetime.datetime(2016, 7, 10)))
        self.event_log.record_event(Event(datetime.datetime(2016, 7, 11)))

    def test_consistency(self):
        try:
            self.event_log.check_consistency()
        except Exception as e:
            self.fail('Raised: {}'.format(e))


class File(object):
    def __init__(self, filepath):
        self._filepath = filepath

    def read(self):
        with open(self._filepath, 'r') as handle:
            text = handle.read()
        return text

    def write(self, data):
        with open(self._filepath, 'w') as handle:
            handle.write(str(data))


class EncryptedFile(object):
    def __init__(self, key, filepath):
        self._filepath = filepath
        self.cryptography_service = CryptographyService(key)

    def read(self):
        with open(self._filepath, 'r') as handle:
            text = handle.read()
        text = self.cryptography_service.decrypt(text)
        return text

    def write(self, data):
        data = self.cryptography_service.encrypt(str(data))
        with open(self._filepath, 'w') as handle:
            handle.write(data)


class EventCollectionSerialisationRoundtripWithFile(unittest.TestCase):

    def setUp(self):
        self.before = EventCollection()
        self.before.add(Event(datetime.datetime(2016, 7, 12)))
        self.before.add(Event(datetime.datetime(2016, 7, 13)))
        f = File('sandbox/bla')
        self.before.to_file(f)

    def test_from_file(self):
        f = File('sandbox/bla')
        after = EventCollection.from_file(f)
        assert_that(self.before.equal_to(after))


class EventCollectionSerialisationRoundtripWithEncryptedFile(unittest.TestCase):

    def setUp(self):
        self.before = EventCollection()
        self.before.add(Event(datetime.datetime(2016, 7, 12)))
        self.before.add(Event(datetime.datetime(2016, 7, 13)))
        f = EncryptedFile('BYT-X2sp5g5c6X5R2KAEaRpoEno4mhARsH00eOMuOy0=', 'sandbox/bla')
        self.before.to_file(f)

    def test_from_encrypted_file(self):
        f = EncryptedFile('BYT-X2sp5g5c6X5R2KAEaRpoEno4mhARsH00eOMuOy0=', 'sandbox/bla')
        after = EventCollection.from_file(f)
        assert_that(self.before.equal_to(after))


class EventTests(unittest.TestCase):

    def test_equality(self):
        assert_that(Event(datetime.datetime(2016, 7, 11)), equal_to(Event(datetime.datetime(2016, 7, 11))))
        assert_that(Event(datetime.datetime(2016, 7, 11)), not_(equal_to(Event(datetime.datetime(2016, 7, 10)))))
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.