def test_application_with_infrastructure(self):
        with self.construct_concrete_application() as app:

            # Start with a new table.
            app.drop_table()
            app.drop_table()
            app.setup_table()
            app.setup_table()

            # Check the notifications.
            reader = NotificationLogReader(app.notification_log)
            old_notifications = reader.list_notifications()
            len_old = len(old_notifications)

            # Check the application's persistence policy,
            # repository, and event store, are working.
            aggregate = ExampleAggregateRoot.__create__()
            aggregate.__save__()
            self.assertTrue(aggregate.id in app.repository)

            # Check the notifications.
            reader = NotificationLogReader(app.notification_log)
            notifications = reader.list_notifications()
            self.assertEqual(1 + len_old, len(notifications))
            topic = "eventsourcing.tests.core_tests.test_aggregate_root#ExampleAggregateRoot.Created"
            self.assertEqual(topic, notifications[len_old]["topic"])

            app.drop_table()
    def test_remote_notification_log(self):
        num_notifications = 42

        section_size = 5

        # Build a notification log (fixture).
        self.append_notifications(num_notifications)

        # Start a simple server.
        from wsgiref.util import setup_testing_defaults
        from wsgiref.simple_server import make_server

        port = 8080
        base_url = "http://127.0.0.1:{}/notifications/".format(port)

        def simple_app(environ, start_response):
            """Simple WSGI application."""
            setup_testing_defaults(environ)

            # Identify log and section from request.
            path_info = environ["PATH_INFO"]
            try:
                section_id = path_info.strip("/").split("/")[-1]
            except ValueError:
                # Start response.
                status = "404 Not Found"
                headers = [("Content-type", "text/plain; charset=utf-8")]
                start_response(status, headers)
                return []

            # Select the notification log.
            notification_log = self.create_notification_log(section_size)

            # Get serialized section.
            json_encoder = ObjectJSONEncoder(separators=JSON_SEPARATORS)
            view = NotificationLogView(notification_log, json_encoder)

            resource = view.present_resource(section_id)
            # Todo: Maybe redirect if the section ID is a mismatch, so
            # the URL is good for cacheing.

            # Start response.
            status = "200 OK"
            headers = [("Content-type", "text/plain; charset=utf-8")]
            start_response(status, headers)

            # Return a list of lines.
            return [(line + "\n").encode("utf8")
                    for line in resource.split("\n")]

        httpd = make_server("", port, simple_app)
        print("Serving on port {}...".format(port))
        thread = Thread(target=httpd.serve_forever)
        thread.setDaemon(True)
        thread.start()
        try:
            # Use reader with client to read all items in remote feed after item 5.
            notification_log = RemoteNotificationLog(base_url)

            # Just before we start, test the deserialise_section_size exceptions.
            notification_log.deserialize_section_size('1')
            with self.assertRaises(ValueError):
                notification_log.deserialize_section_size('"1')
            with self.assertRaises(TypeError):
                notification_log.deserialize_section_size('"1"')

            # Get all the items.
            notification_log_reader = NotificationLogReader(
                notification_log=notification_log)
            items_from_start = notification_log_reader.list_notifications()

            # Check we got all the items.
            self.assertEqual(len(items_from_start), num_notifications)
            self.assertEqual(items_from_start[0]["id"], 1)
            self.assertEqual(items_from_start[0]["state"], b"item1")
            self.assertEqual(
                items_from_start[0]["topic"],
                "eventsourcing.domain.model.events#DomainEvent",
            )
            expected_section_count = ceil(num_notifications /
                                          float(section_size))
            self.assertEqual(notification_log_reader.section_count,
                             expected_section_count)

            # Get all the items from item 5.
            items_from_5 = list(notification_log_reader[section_size - 1:])

            # Check we got everything after item 5.
            self.assertEqual(len(items_from_5),
                             num_notifications - section_size + 1)
            self.assertEqual(items_from_5[0]["id"], section_size)
            self.assertEqual(
                items_from_5[0]["topic"],
                "eventsourcing.domain.model.events#DomainEvent",
            )
            self.assertEqual(items_from_5[0]["state"],
                             "item{}".format(section_size).encode("utf8"))
            expected_section_count = ceil(num_notifications /
                                          float(section_size))
            self.assertEqual(notification_log_reader.section_count,
                             expected_section_count)

            # Check ValueError is raised for deserialization errors.
            with self.assertRaises(ValueError):
                notification_log.deserialize_section("invalid json")

        finally:
            httpd.shutdown()
            thread.join()
            httpd.server_close()
    def test(self):
        # Build notification log.
        section_size = 5
        notification_log = self.create_notification_log(
            section_size=section_size)

        # Append 13 notifications.
        self.append_notifications(13)

        # Construct notification log reader.
        reader = NotificationLogReader(notification_log)

        # Check position.
        self.assertEqual(reader.position, 0)

        # Read all notifications.
        all_notifications = list(reader)
        self.assertEqual(13, len(all_notifications))

        # Check position.
        self.assertEqual(reader.position, 13)

        # Add some more items to the log.
        self.append_notifications(13, 21)

        # Read subsequent notifications.
        subsequent_notifications_notifications = list(reader)
        self.assertEqual(len(subsequent_notifications_notifications), 8)

        # Check position.
        self.assertEqual(reader.position, 21)

        subsequent_notifications_notifications = list(reader)
        self.assertEqual(len(subsequent_notifications_notifications), 0)

        # Set position.
        reader.seek(13)
        subsequent_notifications_notifications = list(reader)
        self.assertEqual(len(subsequent_notifications_notifications), 8)

        # # Read items after a particular position.
        self.assertEqual(len(list(reader[0:])), 21)
        self.assertEqual(len(list(reader[1:])), 20)
        self.assertEqual(len(list(reader[2:])), 19)
        self.assertEqual(len(list(reader[3:])), 18)
        self.assertEqual(len(list(reader[13:])), 8)
        self.assertEqual(len(list(reader[18:])), 3)
        self.assertEqual(len(list(reader[19:])), 2)
        self.assertEqual(len(list(reader[20:])), 1)
        self.assertEqual(len(list(reader[21:])), 0)

        # Check last item numbers less than 1 cause a value errors.
        with self.assertRaises(ValueError):
            reader.position = -1
            list(reader)

        with self.assertRaises(ValueError):
            list(reader.seek(-1))

        # Resume from a saved position.
        saved_position = 5
        advance_by = 3
        reader.seek(saved_position)
        self.assertEqual(reader.position, saved_position)
        reader.list_notifications(advance_by=advance_by)
        self.assertEqual(reader.position, saved_position + advance_by)

        # Read items between particular positions.
        # - check stops at end of slice, and position tracks ok
        self.assertEqual(reader[0]["id"], 1)
        self.assertEqual(reader.position, 1)

        self.assertEqual(next(reader)["id"], 2)
        self.assertEqual(reader.position, 2)

        reader.seek(5)
        self.assertEqual(next(reader)["id"], 6)
        self.assertEqual(reader.position, 6)

        reader.seek(0)
        list(reader)
        self.assertEqual(reader.position, 21)

        self.assertEqual(len(list(reader[0:1])), 1)
        self.assertEqual(reader.position, 1)

        self.assertEqual(len(list(reader[1:3])), 2)
        self.assertEqual(reader.position, 3)

        self.assertEqual(len(list(reader[2:5])), 3)
        self.assertEqual(reader.position, 5)

        self.assertEqual(len(list(reader[3:7])), 4)
        self.assertEqual(reader.position, 7)

        self.assertEqual(len(list(reader[13:20])), 7)
        self.assertEqual(reader.position, 20)

        self.assertEqual(len(list(reader[18:20])), 2)
        self.assertEqual(reader.position, 20)

        self.assertEqual(len(list(reader[19:20])), 1)
        self.assertEqual(reader.position, 20)

        self.assertEqual(len(list(reader[20:20])), 0)
        self.assertEqual(reader.position, 20)

        self.assertEqual(len(list(reader[21:20])), 0)
        self.assertEqual(reader.position, 21)

        with self.assertRaises(StopIteration):
            next(reader)