class TestPizza:
    def setup(self):
        self.graph = create_app(testing=True)
        self.pizza_store = self.graph.pizza_store

        self.name = "NAME"

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        Pizza can be persisted.

        """
        new_pizza = Pizza(customer_id=new_object_id(),
                          crust_type="thin",
                          size=10)

        with transaction():
            self.pizza_store.create(new_pizza)

        retrieved_pizza = self.pizza_store.retrieve(new_pizza.id)
        assert_that(retrieved_pizza, is_(equal_to(new_pizza)))
示例#2
0
class TestOrderEvent:
    def setup(self):
        self.graph = create_app(testing=True)
        self.order_event_store = self.graph.order_event_store

        self.order_id = new_object_id()

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        OrderEvent can be persisted
        """
        new_order = Order(id=self.order_id)
        new_order_event = OrderEvent(
            order_id=self.order_id, event_type=OrderEventType.OrderInitialized)

        with transaction():
            self.graph.order_store.create(new_order)
            self.order_event_store.create(new_order_event)

        retrieved_order_event = self.order_event_store.retrieve(
            new_order_event.id)
        assert_that(retrieved_order_event, is_(new_order_event))
示例#3
0
class TestOrder:
    def setup(self):
        self.graph = create_app(testing=True)
        self.order_store = self.graph.order_store

        self.customer_id = new_object_id()

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        Order can be persisted
        """
        new_order = Order(customer_id=self.customer_id)

        with transaction():
            self.order_store.create(new_order)

        retrieved_order = self.order_store.retrieve(new_order.id)
        assert_that(retrieved_order, is_(new_order))
class TestCloning:
    def setup(self):
        self.graph = create_object_graph(name="example",
                                         testing=True,
                                         import_name="microcosm_postgres")
        self.company_store = self.graph.company_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_clone(self):
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()
            copy = clone(company, dict(name="newname"))

        assert_that(copy.id, is_not(equal_to(company.id)))
        assert_that(self.company_store.retrieve(copy.id), is_(not_none()))
    def setup(self):
        self.graph = create_object_graph(name="example",
                                         testing=True,
                                         import_name="microcosm_postgres")
        self.graph.use("company_store")

        context = SessionContext(self.graph)
        context.recreate_all()
示例#6
0
class TestExamples:
    def setup(self):
        self.graph = create_app(testing=True)
        self.example_store = self.graph.example_store

        self.name = "NAME"

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        Examples can be persisted.

        """
        new_example = Example(name=self.name, )

        with transaction():
            self.example_store.create(new_example)

        retrieved_example = self.example_store.retrieve(new_example.id)
        assert_that(retrieved_example, is_(equal_to(new_example)))

    def test_create_duplicate(self):
        """
        Examples enforce uniqueness on type/external id.

        """
        example1 = Example(name=self.name, )
        example2 = Example(name=self.name, )

        with transaction():
            self.example_store.create(example1)

        assert_that(
            calling(self.example_store.create).with_args(example2),
            raises(DuplicateModelError),
        )

    def test_retrieve_by_name(self):
        """
        Examples can be retrieved by name.

        """
        new_example = Example(name=self.name, )

        with transaction():
            self.example_store.create(new_example)

        retrieved_example = self.example_store.retrieve_by_name(self.name)

        assert_that(retrieved_example, is_(equal_to(new_example)))
class TestCustomerEventStore:

    def setup(self):
        self.graph = create_app(testing=True)
        self.customer_event_store = self.graph.customer_event_store
        self.pizza_store = self.graph.pizza_store
        self.order_store = self.graph.order_store
        self.topping_store = self.graph.topping_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create_customer_started_order(self):
        new_order = Order()

        with transaction():
            new_order = self.order_store.create(new_order)
            customer_event = self.customer_event_store.create(CustomerStartedOrder(order_id=new_order.id))

        retreived_customer_event = self.customer_event_store.retrieve(customer_event.id)
        assert_that(retreived_customer_event, is_(equal_to(customer_event)))

    def test_create_customer_added_topping(self):
        with transaction():
            new_order = self.order_store.create(Order())
            customer_started_order = self.customer_event_store.create(CustomerStartedOrder(order_id=new_order.id))
            customer_added_topping = self.customer_event_store.create(
                CustomerAddedTopping(order_id=new_order.id, topping_type=ToppingType.CHICKEN,
                                     parent_id=customer_started_order.id
                                     ))

        retreived_customer_event = self.customer_event_store.retrieve(customer_added_topping.id)
        assert_that(retreived_customer_event, is_(equal_to(customer_added_topping)))

    def test_delete_order(self):
        new_order = Order().create()
        customer_started_order = self.customer_event_store.create(CustomerStartedOrder(order_id=new_order.id))
        retreived_event = self.customer_event_store.retrieve(customer_started_order.id)
        assert_that(retreived_event, is_(equal_to(customer_started_order)))

        customer_started_order.delete()
        assert_that(
            calling(self.customer_event_store.retrieve).with_args(identifier=customer_started_order.id),
            raises(ModelNotFoundError),
        )
示例#8
0
class TestPizzaStore:
    def setup(self):
        self.graph = create_app(testing=True)
        self.pizza_store = self.graph.pizza_store

        self.pizza_type = PizzaType.HANDTOSSED.name
        self.pizza_size = PizzaSize.SMALL.name

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        self.new_order = Order().create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        new_pizza = Pizza(
            order_id=self.new_order.id,
            pizza_size=self.pizza_size,
            pizza_type=self.pizza_type,
        )

        with transaction():
            self.pizza_store.create(new_pizza)

        retrieved_pizza = self.pizza_store.retrieve(new_pizza.id)
        assert_that(retrieved_pizza, is_(equal_to(new_pizza)))

    def test_create_multiple(self):
        with transaction():
            self.pizza_store.create(
                Pizza(
                    order_id=self.new_order.id,
                    pizza_size=self.pizza_size,
                    pizza_type=self.pizza_type,
                ))

            self.pizza_store.create(
                Pizza(
                    order_id=self.new_order.id,
                    pizza_size=self.pizza_size,
                    pizza_type=self.pizza_type,
                ))

        assert_that(self.pizza_store.search(), has_length(2))
示例#9
0
class TestTweet:
    def setup(self):
        self.graph = create_app(testing=True)
        self.tweet_store = self.graph.tweet_store
        self.user_store = self.graph.user_store

        self.username = "******"
        self.user = User(
            username=self.username,
            email="*****@*****.**",
            first_name="Joe",
            last_name="Chip",
            title="Technician",
            bio="Ubik-- get it today!",
        )

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.user_store.create(self.user)

        self.tweet_content = """
            Friends, this is clean-up time and we’re discounting all our silent,
            electric Ubiks by this much money. Yes, we’re throwing away the blue-book.
            And remember: every Ubik on our lot has been used only as directed.
        """

        self.tweet = Tweet(
            user_id=self.user.id,
            tweet_content=self.tweet_content,
        )

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        with transaction():
            self.tweet_store.create(self.tweet)

        retrieved_tweet = self.tweet_store.retrieve(self.tweet.id)
        assert_that(retrieved_tweet, is_(equal_to(self.tweet)))
示例#10
0
class TestPolymorphicEntityRolledUpEventStore:
    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=join(dirname(__file__), pardir),
            testing=True,
        )
        self.graph.use(
            "sub_task_store",
            "sub_task_event_store",
        )
        self.store = SubTaskRollUpStore(self.graph)

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.sub_task = SubTask().create()
            self.sub_task_created_event = SubTaskEvent(
                event_type=SubTaskEventType.CREATED,
                sub_task_id=self.sub_task.id,
            ).create()
            self.sub_task_assigned_event = SubTaskEvent(
                assignee="Alice",
                event_type=SubTaskEventType.ASSIGNED,
                parent_id=self.sub_task_created_event.id,
                sub_task_id=self.sub_task.id,
            ).create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_retrieve(self):
        rollup = self.store.retrieve(self.sub_task.id)
        assert_that(
            rollup,
            has_properties(
                _event=self.sub_task_assigned_event,
                _container=self.sub_task,
                _rank=1,
                _assignee="Alice",
            ))
示例#11
0
class TestOrderStore:

    def setup(self):
        self.graph = create_app(testing=True)
        self.pizza_store = self.graph.pizza_store
        self.order_store = self.graph.order_store

        self.pizza_type = PizzaType.HANDTOSSED.name
        self.pizza_size = PizzaSize.SMALL.name

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        new_order = Order()

        with transaction():
            self.order_store.create(new_order)

        retreived_order = self.order_store.retrieve(new_order.id)
        assert_that(retreived_order, is_(equal_to(new_order)))

    def test_delete_order(self):
        new_order = Order().create()

        retreived_order = self.order_store.retrieve(new_order.id)
        assert_that(retreived_order, is_(equal_to(new_order)))

        new_order.delete()
        assert_that(
            calling(self.order_store.retrieve).with_args(identifier=new_order.id),
            raises(ModelNotFoundError),
        )
class TestCloning:

    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.company_store = self.graph.company_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_clone(self):
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()
            copy = clone(company, dict(name="newname"))

        assert_that(copy.id, is_not(equal_to(company.id)))
        assert_that(self.company_store.retrieve(copy.id), is_(not_none()))
示例#13
0
class TestUser:
    def setup(self):
        self.graph = create_app(testing=True)
        self.user_store = self.graph.user_store

        self.username = "******"
        self.email = "*****@*****.**"
        self.first_name = "Joe"
        self.last_name = "Chip"
        self.title = "Technician"
        self.bio = "Ubik-- get it today!"

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        new_user = User(
            username=self.username,
            email=self.email,
            first_name=self.first_name,
            last_name=self.last_name,
            title=self.title,
            bio=self.bio,
        )

        with transaction():
            self.user_store.create(new_user)

        retrieved_user = self.user_store.retrieve(new_user.id)
        assert_that(retrieved_user, is_(equal_to(new_user)))

    def test_create_duplicate(self):
        user1 = User(
            username=self.username,
            email=self.email,
            first_name=self.first_name,
            last_name=self.last_name,
            title=self.title,
            bio=self.bio,
        )
        user2 = User(
            username=self.username,
            email=self.email,
            first_name=self.first_name,
            last_name=self.last_name,
            title=self.title,
            bio=self.bio,
        )

        with transaction():
            self.user_store.create(user1)

        assert_that(
            calling(self.user_store.create).with_args(user2),
            raises(DuplicateModelError),
        )

    def test_retrieve_by_username(self):
        new_user = User(
            username=self.username,
            email=self.email,
            first_name=self.first_name,
            last_name=self.last_name,
            title=self.title,
            bio=self.bio,
        )

        with transaction():
            self.user_store.create(new_user)

        retrieved_user = self.user_store.retrieve_by_username(self.username)

        assert_that(retrieved_user, is_(equal_to(new_user)))
class TestRolledUpEventStore:
    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=join(dirname(__file__), pardir),
            testing=True,
        )
        self.graph.use(
            "task_store",
            "task_event_store",
            "activity_store",
            "activity_event_store",
        )
        self.store = TaskRollUpStore(self.graph)

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.task1 = Task().create()
            self.task2 = Task().create()
            self.task1_created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task1.id,
            ).create()
            self.task2_created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task2.id,
            ).create()
            self.task2_assigned_event = TaskEvent(
                assignee="Alice",
                event_type=TaskEventType.ASSIGNED,
                parent_id=self.task2_created_event.id,
                task_id=self.task2.id,
            ).create()
            self.task2_started_event = TaskEvent(
                event_type=TaskEventType.STARTED,
                parent_id=self.task2_assigned_event.id,
                task_id=self.task2.id,
            ).create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_count(self):
        count = self.store.count()
        assert_that(count, is_(equal_to(2)))

    def test_retrieve(self):
        rollup = self.store.retrieve(self.task2.id)
        assert_that(
            rollup,
            has_properties(
                _event=self.task2_started_event,
                _container=self.task2,
                _rank=1,
                _assignee="Alice",
            ))

    def test_retrieve_not_found(self):
        assert_that(
            calling(self.store.retrieve).with_args(new_object_id()),
            raises(ModelNotFoundError),
        )

    def test_search(self):
        results = self.store.search()
        assert_that(results, has_length(2))
        assert_that(
            results,
            contains(
                has_properties(
                    _event=self.task2_started_event,
                    _container=self.task2,
                    _rank=1,
                    _assignee="Alice",
                ),
                has_properties(
                    _event=self.task1_created_event,
                    _container=self.task1,
                    _rank=1,
                ),
            ))

    def test_search_first(self):
        rollup = self.store.search_first()
        assert_that(
            rollup,
            has_properties(
                _event=self.task2_started_event,
                _container=self.task2,
                _rank=1,
                _assignee="Alice",
            ))

    def test_search_first_without_response(self):
        rollup = self.store.search_first(asignee="Julio")
        assert_that(rollup, is_(equal_to(None)))

    def test_filter(self):
        results = self.store.search(asignee="Alice")
        assert_that(results, has_length(1))
        assert_that(
            results,
            contains(
                has_properties(
                    _event=self.task2_started_event,
                    _container=self.task2,
                    _rank=1,
                    _assignee="Alice",
                ), ))

    def test_exact_count(self):
        count = self.store.count(asignee="Alice")
        exact_count = self.store.exact_count(asignee="Alice")
        assert_that(count, is_(equal_to(2)))
        assert_that(exact_count, is_(equal_to(1)))
示例#15
0
class TestMigrations:

    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=join(dirname(__file__), pardir),
            testing=True,
        )
        self.graph.use(
            "task_store",
            "task_event_store",
            "activity_store",
            "activity_event_store",
        )
        self.store = self.graph.task_event_store
        self.activity_store = self.graph.activity_event_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.task = Task().create()
            self.created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            ).create()
            self.scheduled_event = TaskEvent(
                deadline=datetime.utcnow(),
                event_type=TaskEventType.SCHEDULED,
                parent_id=self.created_event.id,
                state=[TaskEventType.CREATED, TaskEventType.SCHEDULED],
                task_id=self.task.id,
            ).create()
            self.assigned_event = TaskEvent(
                deadline=datetime.utcnow(),
                event_type=TaskEventType.ASSIGNED,
                parent_id=self.scheduled_event.id,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                assignee="assignee",
                task_id=self.task.id,
            ).create()
            self.started_event = TaskEvent(
                event_type=TaskEventType.STARTED,
                parent_id=self.assigned_event.id,
                task_id=self.task.id,
            ).create()
            # flush sqlalchemy cache before sql operation
            self.store.session.expire_all()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_proc_event_type_delete(self):
        """
        Test that sql proc_event_type_delete function:
        * delete events
        * replaces parent_id
        * updates states

        """
        with transaction():
            self.store.session.execute("SELECT proc_event_type_delete('task_event', 'SCHEDULED', 'task_id');")

        results = self.store.search()
        assert_that(results, has_length(3))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED],
                id=self.assigned_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_proc_event_type_replace(self):
        """
        Test that proc_event_type_replace sql function:
        * replace event_type
        * replace event_types in state (and sort it)

        """
        with transaction():
            self.store.session.execute("SELECT proc_event_type_replace('task_event', 'SCHEDULED', 'CANCELED');")

        results = self.store.search()
        assert_that(results, has_length(4))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CANCELED, TaskEventType.CREATED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CANCELED,
                state=[TaskEventType.CANCELED, TaskEventType.CREATED],
                id=self.scheduled_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_delete_single_event(self):
        """
        Test that sql proc_events_delete function:
        * delete events
        * replaces parent_id

        """
        with transaction():
            self.store.session.execute("""
                CREATE TEMP TABLE events_to_remove AS (
                    SELECT id FROM task_event WHERE event_type='SCHEDULED'
                );
                SELECT proc_events_delete('task_event', 'events_to_remove', 'task_id');
            """)

        results = self.store.search()
        assert_that(results, has_length(3))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.assigned_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_edge_case_proc_event_type_replace_remove_duplicates(self):
        """
        Test that proc_event_type_replace sql function:
        * replace event_type
        * replace event_types in state and remove duplicates (and sort it)

        """
        with transaction():
            self.store.session.execute("SELECT proc_event_type_replace('task_event', 'SCHEDULED', 'CREATED');")

        results = self.store.search()
        assert_that(results, has_length(4))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.scheduled_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_edge_case_delete_number_of_events(self):
        """
        Test that sql proc_events_delete function:
        * delete events
        * replaces parent_id (even if the new parent is not directly refrenced by the deleted event)

        """
        with transaction():
            self.store.session.execute("""
                CREATE TEMP TABLE events_to_remove AS (
                    SELECT id FROM task_event WHERE event_type='SCHEDULED' OR event_type='ASSIGNED'
                );
                SELECT proc_events_delete('task_event', 'events_to_remove', 'task_id');
            """)

        results = self.store.search()
        assert_that(results, has_length(2))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_edge_case_delete_last_event(self):
        """
        Test that proc_event_type_replace sql function can delete last event of a model

        """
        with transaction():
            self.store.session.execute("""
                CREATE TEMP TABLE events_to_remove AS (
                    SELECT id FROM task_event WHERE event_type='STARTED'
                );
                SELECT proc_events_delete('task_event', 'events_to_remove', 'task_id');
            """)

        results = self.store.search()
        assert_that(results, has_length(3))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.SCHEDULED,
                state=[TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.scheduled_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_edge_case_delete_first_event(self):
        """
        Can delete first events (But still have to follow "require_{}_parent_id" constraint)

        """
        with transaction():
            self.activity_store.session.execute(
                """
                    CREATE TEMP TABLE events_to_remove AS (
                        SELECT id FROM task_event WHERE event_type='CREATED'
                    );
                    SELECT proc_event_type_replace('task_event', 'SCHEDULED', 'CREATED');
                    SELECT proc_events_delete('task_event', 'events_to_remove', 'task_id');
                """
            )
            self.store.session.expire_all()
        results = self.store.search()
        assert_that(results, has_length(3))
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.scheduled_event.id,
                parent_id=None,
            ),
        ))

    def test_edge_case_proc_events_delete_with_no_parent_id_constraint(self):
        """
        Some events dont have a parent_id_constraint (If model.__unique_parent__ is set to False)
        In order to delete them, we have to call:
            "proc_events_delete_with_no_parent_id_constraint" instead of "proc_events_delete".
        We cannot call "proc_events_delete_with_no_parent_id_constraint" for events with constraint.

        """
        with transaction():
            activity = Activity().create()
            created_event = ActivityEvent(
                event_type=ActivityEventType.CREATED,
                activity_id=activity.id,
            ).create()
            ActivityEvent(
                event_type=ActivityEventType.CANCELED,
                parent_id=created_event.id,
                activity_id=activity.id,
            ).create()
            self.activity_store.session.expire_all()

        with transaction():
            assert_that(calling(self.activity_store.session.execute).with_args(
                """
                    CREATE TEMP TABLE events_to_remove AS (
                        SELECT id FROM activity_event WHERE event_type='CANCELED'
                    );
                    SELECT proc_events_delete('activity_event', 'events_to_remove', 'activity_id');
                """
                ), raises(ProgrammingError))

        with transaction():
            assert_that(calling(self.activity_store.session.execute).with_args(
                """
                    CREATE TEMP TABLE events_to_remove AS (
                        SELECT id FROM task_event WHERE event_type='SCHEDULED'
                    );
                    SELECT proc_events_delete_with_no_parent_id_constraint('task_event', 'events_to_remove', 'task_id');
                """
                ), raises(IntegrityError))

        with transaction():
            self.activity_store.session.execute("""
                CREATE TEMP TABLE events_to_remove AS (
                    SELECT id FROM activity_event WHERE event_type='CANCELED'
                );
                SELECT proc_events_delete_with_no_parent_id_constraint(
                    'activity_event',
                    'events_to_remove',
                    'activity_id'
                );
            """)

        results = self.activity_store.search()
        assert_that(results, has_length(1))
        assert_that(results, contains(
            has_properties(
                event_type=ActivityEventType.CREATED,
                state=[ActivityEventType.CREATED],
                id=created_event.id,
                parent_id=None,
            ),
        ))

    def test_proc_events_create(self):
        """
        Insert a revised event before the started event and after the assigned event.

        """
        reassigned_event_id = new_object_id()

        events_to_create_string = (
            f"CREATE TEMP TABLE events_to_create AS (\n"
            f"       SELECT\n"
            f"          '{reassigned_event_id}'::uuid as id,\n"
            f"          extract(epoch from now()) as created_at,\n"
            f"          extract(epoch from now()) as updated_at,\n"
            f"          assignee,\n"
            f"          NULL::timestamp without time zone as deadline,\n"
            f"          task_id,\n"
            f"          'REASSIGNED' as event_type,\n"
            f"          id as parent_id,\n"
            f"          state,\n"
            f"          1 as version\n"
            f"       FROM task_event WHERE event_type='ASSIGNED'\n"
            f"    );"
        )

        self.activity_store.session.execute(events_to_create_string)

        self.activity_store.session.execute("""
            SELECT proc_events_create(
                'task_event',
                'events_to_create',
                '(
                    id,
                    created_at,
                    updated_at,
                    assignee,
                    deadline,
                    task_id,
                    event_type,
                    parent_id,
                    state,
                    version
                )'
            );
        """)
        results = self.store.search()
        assert_that(results, has_length(5))

        # NB: The events appear out of order because they are sorted by clock,
        # but the parent id chain is correct. In particular the parent of the
        # STARTED event has been changed by the migration
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.REASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                parent_id=self.assigned_event.id,
                id=reassigned_event_id,
            ),
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=reassigned_event_id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.SCHEDULED,
                state=[TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.scheduled_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))

    def test_proc_events_create_end_event(self):
        """
        Insert a canceled event at the end of the event stream.

        """
        cancelled_event_id = new_object_id()

        events_to_create_string = (
            f"CREATE TEMP TABLE events_to_create AS (\n"
            f"       SELECT\n"
            f"          '{cancelled_event_id}'::uuid as id,\n"
            f"          extract(epoch from now()) as created_at,\n"
            f"          extract(epoch from now()) as updated_at,\n"
            f"          assignee,\n"
            f"          NULL::timestamp without time zone as deadline,\n"
            f"          task_id,\n"
            f"          'CANCELED' as event_type,\n"
            f"          id as parent_id,\n"
            f"          '{{\"CANCELED\"}}'::character varying[] as state,\n"
            f"          1 as version\n"
            f"       FROM task_event WHERE event_type='STARTED'\n"
            f"    );"
        )

        self.activity_store.session.execute(events_to_create_string)

        self.activity_store.session.execute("""
            SELECT proc_events_create(
                'task_event',
                'events_to_create',
                '(
                    id,
                    created_at,
                    updated_at,
                    assignee,
                    deadline,
                    task_id,
                    event_type,
                    parent_id,
                    state,
                    version
                )'
            );
        """)
        results = self.store.search()
        assert_that(results, has_length(5))

        # NB: The events appear out of order because they are sorted by clock,
        # but the parent id chain is correct. In particular the parent of the
        # STARTED event has been changed by the migration
        assert_that(results, contains(
            has_properties(
                event_type=TaskEventType.CANCELED,
                state=[TaskEventType.CANCELED],
                parent_id=self.started_event.id,
                id=cancelled_event_id,
            ),
            has_properties(
                event_type=TaskEventType.STARTED,
                state=[TaskEventType.STARTED],
                id=self.started_event.id,
                parent_id=self.assigned_event.id,
            ),
            has_properties(
                event_type=TaskEventType.ASSIGNED,
                state=[TaskEventType.ASSIGNED, TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.assigned_event.id,
                parent_id=self.scheduled_event.id,
            ),
            has_properties(
                event_type=TaskEventType.SCHEDULED,
                state=[TaskEventType.CREATED, TaskEventType.SCHEDULED],
                id=self.scheduled_event.id,
                parent_id=self.created_event.id,
            ),
            has_properties(
                event_type=TaskEventType.CREATED,
                state=[TaskEventType.CREATED],
                id=self.created_event.id,
                parent_id=None,
            ),
        ))
示例#16
0
class TestCompany:
    def setup(self):
        self.graph = create_object_graph(name="example",
                                         testing=True,
                                         import_name="microcosm_postgres")
        self.company_store = self.graph.company_store
        self.employee_store = self.graph.employee_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create_retrieve_company(self):
        """
        Should be able to retrieve a company after creating it.

        """
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()

        retrieved_company = Company.retrieve(company.id)
        assert_that(retrieved_company.name, is_(equal_to("name")))
        assert_that(retrieved_company.type, is_(equal_to(CompanyType.private)))

    def test_search_company(self):
        """
        Should be able to search for companies.

        """
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()

        assert_that(Company.search(), contains(company))
        assert_that(Company.search(name="whatever"), is_(empty()))
        assert_that(Company.search(name=company.name), contains(company))
        # NB: filtering is skipped if None
        assert_that(Company.search(name=None), contains(company))

    def test_create_duplicate_company(self):
        """
        Should not be able to retrieve a company with a duplicate name.

        """
        with transaction():
            Company(name="name").create()

        company = Company(name="name")
        assert_that(calling(company.create), raises(DuplicateModelError))

    def test_create_delete_company(self):
        """
        Should not be able to retrieve a company after deleting it.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            company.delete()

        assert_that(
            calling(Company.retrieve).with_args(company.id),
            raises(ModelNotFoundError, pattern="Company not found"),
        )

    def test_create_delete_company_complicated_expression(self):
        """
        Delete should support more complicated criterion with the `fetch` synchronization strategy enabled.

        """
        with transaction():
            company = Company(name="name").create()
            Company(name="other-name").create()

        with transaction():
            self.company_store._delete(Company.name.in_(["name"]),
                                       synchronize_session="fetch")

        assert_that(
            calling(Company.retrieve).with_args(company.id),
            raises(ModelNotFoundError, pattern="Company not found"),
        )

        assert_that(Company.count(), is_(equal_to(1)))

    def test_create_search_count_company(self):
        """
        Should be able to search and count companies after creation.

        """
        with transaction():
            company1 = Company(name="name1").create()
            company2 = Company(name="name2").create()

        assert_that(Company.count(), is_(equal_to(2)))

        # Pagination fields do not affect count calculations
        assert_that(self.company_store.count(offset=1, limit=1),
                    is_(equal_to(2)))

        assert_that([company.id for company in Company.search()],
                    contains_inanyorder(company1.id, company2.id))

    def test_create_update_company(self):
        """
        Should be able to update a company after creating it.

        """
        with transaction():
            company = Company(name="name", ).create()

        with transaction():
            updated_company = Company(
                id=company.id,
                name="new_name",
            ).update()
            assert_that(updated_company.name, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_with_diff_company(self):
        """
        Should be able to update a company after creating it and get a diff.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            _, diff = Company(
                id=company.id,
                name="new_name",
            ).update_with_diff()
            assert_that(list(diff.keys()),
                        contains_inanyorder("name", "updated_at"))
            assert_that(diff["name"].before, is_(equal_to("name")))
            assert_that(diff["name"].after, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_duplicate_company(self):
        """
        Should be not able to update a company to a duplicate name.

        """
        with transaction():
            Company(name="name1").create()
            company = Company(name="name2").create()

        company.name = "name1"
        assert_that(calling(company.update), raises(DuplicateModelError))

    def test_delete_company_with_employees(self):
        """
        Should be not able to delete a company with employees.

        """
        with transaction():
            Company(name="name1", ).create()
            company = Company(name="name2", ).create()

        with transaction():
            Employee(
                first="first",
                last="last",
                company_id=company.id,
            ).create()

        assert_that(calling(company.delete), raises(ReferencedModelError))
class TestFollowerRelationship:
    def setup(self):
        self.graph = create_app(testing=True)
        self.user_store = self.graph.user_store
        self.follower_relationship_store = self.graph.follower_relationship_store

        self.username1 = "glen.runciter"
        self.user1 = User(
            username=self.username1,
            email="*****@*****.**",
            first_name="Glen",
            last_name="Runciter",
            title="Big Boss",
            bio="Ubik-- get it today!",
        )

        self.username2 = "joe.chip"
        self.user2 = User(
            username=self.username2,
            email="*****@*****.**",
            first_name="Joe",
            last_name="Chip",
            title="Technician",
            bio="Ubik-- get it today!",
        )

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.user1.create()
            self.user2.create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        new_follower_relationship = FollowerRelationship(
            user_id=self.user1.id,
            follower_id=self.user2.id,
        )

        with transaction():
            self.follower_relationship_store.create(new_follower_relationship)

        retrieved_follower_relationship = self.follower_relationship_store.retrieve(
            new_follower_relationship.id)
        assert_that(retrieved_follower_relationship,
                    is_(equal_to(new_follower_relationship)))

    def test_create_duplicate(self):
        follower_relationship1 = FollowerRelationship(
            user_id=self.user1.id,
            follower_id=self.user2.id,
        )
        follower_relationship2 = FollowerRelationship(
            user_id=self.user1.id,
            follower_id=self.user2.id,
        )

        with transaction():
            self.follower_relationship_store.create(follower_relationship1)

        assert_that(
            calling(self.follower_relationship_store.create).with_args(
                follower_relationship2),
            raises(DuplicateModelError),
        )
示例#18
0
class TestRolledUpEventStore:

    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=join(dirname(__file__), pardir),
            testing=True,
        )
        self.graph.use(
            "simple_test_object_store",
            "simple_test_object_event_store",
        )
        self.store = SimpleObjectTestRollupStore(self.graph)
        self.event_store = self.graph.simple_test_object_event_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def create_events(self, until=None, **kwargs):
        events = []
        for event in self.iter_events(**kwargs):
            if event.event_type == str(until):
                return events
            events.append(event)
        return events

    def iter_events(self, simple_test_object=None):
        with transaction():
            event = self.event_store.create(
                SimpleTestObjectEvent(
                    event_type=str(SimpleTestObjectEventType.CREATED),
                    simple_test_object_id=simple_test_object.id,
                ),
            )

        yield event

        with transaction():
            event = self.event_store.create(
                SimpleTestObjectEvent(
                    event_type=str(SimpleTestObjectEventType.READY),
                    parent_id=event.id,
                    simple_test_object_id=simple_test_object.id,
                ),
            )

        yield event

        with transaction():
            event = self.event_store.create(
                SimpleTestObjectEvent(
                    event_type=str(SimpleTestObjectEventType.DONE),
                    parent_id=event.id,
                    simple_test_object_id=simple_test_object.id,
                ),
            )

        yield event

    def test_search_by_limit(self):
        with transaction():
            object1 = SimpleTestObject().create()
            object2 = SimpleTestObject().create()
            object3 = SimpleTestObject().create()
            object4 = SimpleTestObject().create()

            self.create_events(simple_test_object=object1, until=SimpleTestObjectEventType.CREATED)
            self.create_events(simple_test_object=object2, until=SimpleTestObjectEventType.READY)
            self.create_events(simple_test_object=object3, until=SimpleTestObjectEventType.DONE)
            self.create_events(simple_test_object=object4, until=SimpleTestObjectEventType.DONE)

            result = self.store.search()

            assert_that(result, has_length(4))
            assert_that(
                result,
                contains_inanyorder(
                    has_properties(
                        id=object1.id,
                        _event_type=str(SimpleTestObjectEventType.CREATED),
                    ),
                    has_properties(
                        id=object2.id,
                        _event_type=str(SimpleTestObjectEventType.READY),
                    ),
                    has_properties(
                        id=object3.id,
                        _event_type=str(SimpleTestObjectEventType.DONE),
                    ),
                    has_properties(
                        id=object4.id,
                        _event_type=str(SimpleTestObjectEventType.DONE),
                    ),
                ),
            )

            result = self.store.search(event_type=str(SimpleTestObjectEventType.DONE))

            assert_that(result, has_length(2))
            assert_that(
                result,
                contains_inanyorder(
                    has_properties(
                        id=object3.id,
                        _event_type=str(SimpleTestObjectEventType.DONE),
                    ),
                    has_properties(
                        id=object4.id,
                        _event_type=str(SimpleTestObjectEventType.DONE),
                    ),
                ),
            )
示例#19
0
class TestSequential(object):
    def setup(self):
        self.graph = create_object_graph(name="example",
                                         testing=True,
                                         import_name="microcosm_postgres")
        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()
        self.store = Store(self.graph, Sequential)

    def teardown(self):
        self.context.close()

    def test_create_sequence_values(self):
        """
        Creating new values should trigger auto increments.

        """
        with transaction():
            examples = [self.store.create(Sequential()) for _ in range(10)]

        for index, example in enumerate(examples):
            assert_that(examples[index].id, is_(not_none()))
            assert_that(examples[index].value, is_(equal_to(index + 1)))

    def test_retrieve_sequence_value(self):
        """
        Retrieving existing values should return the previously generated sequence.

        """
        with transaction():
            example = self.store.create(Sequential())

        self.context.session.expunge(example)

        example = self.store.retrieve(example.id)

        assert_that(example.value, is_(equal_to(1)))

    def test_retrieve_by_sequence_value(self):
        """
        Retrieving existing values should return the previously generated sequence.

        """
        with transaction():
            example = self.store.create(Sequential())

        retrieved = self.store._query().filter(
            Sequential.value == example.value, ).one()
        assert_that(retrieved.id, is_(equal_to(example.id)))

    def test_update_sequence(self):
        """
        Updating a sequence is allowed (but not advised).

        """
        with transaction():
            example = self.store.create(Sequential())

        example.value = example.value + 1

        with transaction():
            self.store.replace(example.id, example)

        self.context.session.expunge(example)

        example = self.store.retrieve(example.id)

        assert_that(example.value, is_(equal_to(2)))

    def test_delete_does_not_reset_sequence(self):
        """
        Deletion does not reset the sequence.

        """
        with transaction():
            example = self.store.create(Sequential())

        with transaction():
            self.store.delete(example.id)

        with transaction():
            example = self.store.create(Sequential())

        assert_that(example.value, is_(equal_to(2)))
示例#20
0
class TestEmployee(object):

    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.company_store = self.graph.company_store
        self.employee_store = self.graph.employee_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()
        with transaction():
            self.company = Company(name="name").create()

    def teardown(self):
        self.context.close()

    def test_create_employee(self):
        """
        Should be able to retrieve an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        retrieved_employee = Employee.retrieve(employee.id)
        assert_that(retrieved_employee.first, is_(equal_to("first")))
        assert_that(retrieved_employee.last, is_(equal_to("last")))

    def test_create_employee_without_company(self):
        """
        Should not be able to create an employee without a company.

        """
        employee = Employee(
            first="first",
            last="last",
        )

        assert_that(calling(employee.create), raises(ModelIntegrityError))

    def test_update_employee_that_exists(self):
        """
        Should be able to update an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            updated_employee = Employee(
                id=employee.id,
                first="Jane",
                last="Doe",
            ).update()
            assert_that(updated_employee.first, is_(equal_to("Jane")))
            assert_that(updated_employee.last, is_(equal_to("Doe")))
            assert_that(updated_employee.company_id, is_(equal_to(self.company.id)))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("Jane")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_update_with_diff_employee_that_exists(self):
        """
        Should be able to update an employee after creating it and get a diff.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            _, diff = Employee(
                id=employee.id,
                last="Doe",
            ).update_with_diff()
            assert_that(list(diff.keys()), contains_inanyorder("last", "updated_at"))
            assert_that(diff["last"].before, is_(equal_to("last")))
            assert_that(diff["last"].after, is_(equal_to("Doe")))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("first")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_update_employee_that_does_not_exit(self):
        """
        Should not be able to update an employee that does not exist.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            )
            assert_that(calling(employee.update), raises(ModelNotFoundError))

    def test_replace_employee_that_exists(self):
        """
        Should be able to replace an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            updated_employee = Employee(
                id=employee.id,
                first="Jane",
                last="Doe",
            ).replace()
            assert_that(updated_employee.first, is_(equal_to("Jane")))
            assert_that(updated_employee.last, is_(equal_to("Doe")))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("Jane")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_replace_employee_that_does_not_exist(self):
        """
        Should be able to replace an employee that does not exist.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).replace()

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("first")))
            assert_that(retrieved_employee.last, is_(equal_to("last")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_search_for_employees_by_company(self):
        """
        Should be able to retrieve an employee after creating it.

        """
        with transaction():
            employee1 = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()
            employee2 = Employee(
                first="Jane",
                last="Doe",
                company_id=self.company.id,
            ).create()
            company2 = Company(name="other").create()
            employee3 = Employee(
                first="John",
                last="Doe",
                company_id=company2.id,
            ).create()

        assert_that(Employee.count(), is_(equal_to(3)))
        assert_that(
            [employee.last for employee in self.employee_store.search_by_company(self.company.id)],
            contains("Doe", "last"),
        )
        assert_that(
            [employee.id for employee in self.employee_store.search_by_company(self.company.id)],
            contains_inanyorder(employee1.id, employee2.id)
        )
        assert_that(
            [employee.id for employee in self.employee_store.search_by_company(company2.id)],
            contains_inanyorder(employee3.id)
        )

    def test_search_filter_employees_by_company(self):
        """
        Should be able to filter searches using kwargs.

        """
        with transaction():
            employee1 = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()
            employee2 = Employee(
                first="Jane",
                last="Doe",
                company_id=self.company.id,
            ).create()
            company2 = Company(name="other").create()
            employee3 = Employee(
                first="John",
                last="Doe",
                company_id=company2.id,
            ).create()

        assert_that(Employee.count(), is_(equal_to(3)))
        assert_that(
            [employee.id for employee in self.employee_store.search(company_id=self.company.id, offset=0, limit=10)],
            contains_inanyorder(employee1.id, employee2.id)
        )
        assert_that(self.employee_store.count(company_id=self.company.id, offset=0, limit=10), is_(equal_to(2)))
        assert_that(
            [employee.id for employee in self.employee_store.search(company_id=company2.id, offset=0, limit=10)],
            contains_inanyorder(employee3.id)
        )
        assert_that(self.employee_store.count(company_id=company2.id, offset=0, limit=10), is_(equal_to(1)))
class TestCompany:

    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.company_store = self.graph.company_store
        self.employee_store = self.graph.employee_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create_retrieve_company(self):
        """
        Should be able to retrieve a company after creating it.

        """
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()

        retrieved_company = Company.retrieve(company.id)
        assert_that(retrieved_company.name, is_(equal_to("name")))
        assert_that(retrieved_company.type, is_(equal_to(CompanyType.private)))

    def test_search_company(self):
        """
        Should be able to search for companies.

        """
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()

        assert_that(Company.search(), contains(company))
        assert_that(Company.search(name="whatever"), is_(empty()))
        assert_that(Company.search(name=company.name), contains(company))
        # NB: filtering is skipped if None
        assert_that(Company.search(name=None), contains(company))

    def test_create_duplicate_company(self):
        """
        Should not be able to retrieve a company with a duplicate name.

        """
        with transaction():
            Company(name="name").create()

        company = Company(name="name")
        assert_that(calling(company.create), raises(DuplicateModelError))

    def test_create_delete_company(self):
        """
        Should not be able to retrieve a company after deleting it.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            company.delete()

        assert_that(
            calling(Company.retrieve).with_args(company.id),
            raises(ModelNotFoundError, pattern="Company not found"),
        )

    def test_create_search_count_company(self):
        """
        Should be able to search and count companies after creation.

        """
        with transaction():
            company1 = Company(name="name1").create()
            company2 = Company(name="name2").create()

        assert_that(Company.count(), is_(equal_to(2)))

        # Pagination fields do not affect count calculations
        assert_that(self.company_store.count(offset=1, limit=1), is_(equal_to(2)))

        assert_that([company.id for company in Company.search()], contains_inanyorder(company1.id, company2.id))

    def test_create_update_company(self):
        """
        Should be able to update a company after creating it.

        """
        with transaction():
            company = Company(
                name="name",
            ).create()

        with transaction():
            updated_company = Company(
                id=company.id,
                name="new_name",
            ).update()
            assert_that(updated_company.name, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_with_diff_company(self):
        """
        Should be able to update a company after creating it and get a diff.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            _, diff = Company(
                id=company.id,
                name="new_name",
            ).update_with_diff()
            assert_that(list(diff.keys()), contains_inanyorder("name", "updated_at"))
            assert_that(diff["name"].before, is_(equal_to("name")))
            assert_that(diff["name"].after, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_duplicate_company(self):
        """
        Should be not able to update a company to a duplicate name.

        """
        with transaction():
            Company(name="name1").create()
            company = Company(name="name2").create()

        company.name = "name1"
        assert_that(calling(company.update), raises(DuplicateModelError))

    def test_delete_company_with_employees(self):
        """
        Should be not able to delete a company with employees.

        """
        with transaction():
            Company(
                name="name1",
            ).create()
            company = Company(
                name="name2",
            ).create()

        with transaction():
            Employee(
                first="first",
                last="last",
                company_id=company.id,
            ).create()

        assert_that(calling(company.delete), raises(ReferencedModelError))
class TestExamples:

    def setup(self):
        self.graph = create_app(testing=True)
        self.example_store = self.graph.example_store

        self.name = "NAME"

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        Examples can be persisted.

        """
        new_example = Example(
            name=self.name,
        )

        with transaction():
            self.example_store.create(new_example)

        retrieved_example = self.example_store.retrieve(new_example.id)
        assert_that(retrieved_example, is_(equal_to(new_example)))

    def test_create_duplicate(self):
        """
        Examples enforce uniqueness on type/external id.

        """
        example1 = Example(
            name=self.name,
        )
        example2 = Example(
            name=self.name,
        )

        with transaction():
            self.example_store.create(example1)

        assert_that(
            calling(self.example_store.create).with_args(example2),
            raises(DuplicateModelError),
        )

    def test_retrieve_by_name(self):
        """
        Examples can be retrieved by name.

        """
        new_example = Example(
            name=self.name,
        )

        with transaction():
            self.example_store.create(new_example)

        retrieved_example = self.example_store.retrieve_by_name(
            self.name
        )

        assert_that(retrieved_example, is_(equal_to(new_example)))
示例#23
0
class TestToppingStore:
    def setup(self):
        self.graph = create_app(testing=True)
        self.topping_store = self.graph.topping_store
        self.pizza_store = self.graph.pizza_store

        self.pizza_type = PizzaType.HANDTOSSED.name
        self.pizza_size = PizzaSize.SMALL.name

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        self.new_order = Order().create()

        self.new_pizza = Pizza(
            order_id=self.new_order.id,
            pizza_size=self.pizza_size,
            pizza_type=self.pizza_type,
        ).create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        new_topping = Topping(
            pizza_id=self.new_pizza.id,
            topping_type=ToppingType.CHICKEN,
        )

        with transaction():
            self.topping_store.create(new_topping)

        retrieved_topping = self.topping_store.retrieve(new_topping.id)
        assert_that(retrieved_topping, is_(equal_to(new_topping)))

    def test_search_toppings(self):
        Topping(
            pizza_id=self.new_pizza.id,
            topping_type=ToppingType.CHICKEN,
        ).create()
        Topping(
            pizza_id=self.new_pizza.id,
            topping_type=ToppingType.ONIONS,
        ).create()

        assert_that(self.topping_store.search(pizza_id=self.new_pizza.id),
                    has_length(2))

    def test_delete_topping(self):
        assert_that(self.topping_store.search(pizza_id=self.new_pizza.id),
                    has_length(0))
        new_topping = Topping(
            pizza_id=self.new_pizza.id,
            topping_type=ToppingType.CHICKEN,
        ).create()

        assert_that(self.topping_store.search(pizza_id=self.new_pizza.id),
                    has_length(1))

        new_topping.delete()
        assert_that(self.topping_store.search(pizza_id=self.new_pizza.id),
                    has_length(0))
示例#24
0
class TestEventStore:
    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=join(dirname(__file__), pardir),
            testing=True,
        )
        self.graph.use(
            "task_store",
            "task_event_store",
            "activity_store",
            "activity_event_store",
        )
        self.store = self.graph.task_event_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.task = Task().create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_column_declarations(self):
        assert_that(TaskEvent.clock, is_(not_none()))
        assert_that(TaskEvent.event_type, is_(not_none()))
        assert_that(TaskEvent.task_id, is_(not_none()))
        assert_that(TaskEvent.parent_id, is_(not_none()))
        assert_that(TaskEvent.version, is_(not_none()))

    def test_column_alias(self):
        assert_that(TaskEvent.container_id, is_(equal_to(TaskEvent.task_id)))

        task_event = TaskEvent()
        task_event.container_id = self.task.id
        assert_that(task_event.container_id, is_(equal_to(self.task.id)))
        assert_that(task_event.task_id, is_(equal_to(self.task.id)))

    def test_create_retrieve(self):
        """
        An event can be retrieved after it is created.

        """
        with transaction():
            task_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            )
            self.store.create(task_event)

        assert_that(task_event.clock, is_(equal_to(1)))
        assert_that(task_event.parent_id, is_(none()))
        assert_that(task_event.state, contains(TaskEventType.CREATED))
        assert_that(task_event.version, is_(equal_to(1)))

        assert_that(
            self.store.retrieve(task_event.id),
            is_(equal_to(task_event)),
        )

    def test_non_initial_event_requires_parent_id(self):
        """
        A non-initial event must have a previous event.

        """
        task_event = TaskEvent(
            event_type=TaskEventType.STARTED,
            task_id=self.task.id,
        )
        assert_that(
            calling(self.store.create).with_args(task_event),
            raises(ModelIntegrityError),
        )

    def test_multi_valued_state(self):
        """
        A state can contain multiple values.

        """
        with transaction():
            task_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                state=(TaskEventType.CREATED, TaskEventType.ASSIGNED),
                task_id=self.task.id,
            )
            self.store.create(task_event)

        assert_that(
            task_event.state,
            contains_inanyorder(TaskEventType.ASSIGNED, TaskEventType.CREATED),
        )

    def test_retrieve_most_recent(self):
        """
        The logical clock provides a total ordering on the main foreign key.

        """
        with transaction():
            created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            )
            self.store.create(created_event)
            assigned_event = TaskEvent(
                assignee="Alice",
                event_type=TaskEventType.ASSIGNED,
                parent_id=created_event.id,
                task_id=self.task.id,
            )
            self.store.create(assigned_event)

        assert_that(
            self.store.retrieve_most_recent(task_id=self.task.id),
            is_(equal_to(assigned_event)),
        )

    def test_unique_parent_id(self):
        """
        Events are unique per parent.

        """
        with transaction():
            created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            )
            self.store.create(created_event)
            task_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                parent_id=created_event.id,
                task_id=self.task.id,
            )
            self.store.create(task_event)

        assert_that(
            calling(self.store.create).with_args(
                TaskEvent(
                    event_type=TaskEventType.CREATED,
                    parent_id=created_event.id,
                    task_id=self.task.id,
                ), ),
            raises(DuplicateModelError),
        )

    def test_upsert_on_index_elements(self):
        """
        Events with a duplicate index elements can be upserted.

        """
        with transaction():
            created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            )
            self.store.create(created_event)
            task_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                parent_id=created_event.id,
                task_id=self.task.id,
            )
            self.store.create(task_event)

        upserted = self.store.upsert_on_index_elements(
            TaskEvent(
                event_type=TaskEventType.CREATED,
                parent_id=created_event.id,
                task_id=self.task.id,
            ))

        assert_that(task_event.id, is_(equal_to(upserted.id)))

    def test_upsert_on_index_elements_mismatch(self):
        """
        Events with a duplicate index elements cannot be upsert if they don't match.

        """
        with transaction():
            created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            )
            self.store.create(created_event)
            task_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                parent_id=created_event.id,
                task_id=self.task.id,
            )
            self.store.create(task_event)

        assert_that(
            calling(self.store.upsert_on_index_elements).with_args(
                TaskEvent(
                    event_type=TaskEventType.REVISED,
                    parent_id=created_event.id,
                    task_id=self.task.id,
                )),
            raises(ConcurrentStateConflictError),
        )

    def test_multiple_children_per_parent(self):
        """
        Events are not unique per parent for False unique_parent events.

        """
        with transaction():
            self.activity = Activity().create()
            created_event = ActivityEvent(
                event_type=ActivityEventType.CREATED,
                activity_id=self.activity.id,
            )
            self.store.create(created_event)
            task_event = ActivityEvent(
                event_type=ActivityEventType.CANCELED,
                parent_id=created_event.id,
                activity_id=self.activity.id,
            )
            self.store.create(task_event)
            same_parent_task_event = ActivityEvent(
                event_type=ActivityEventType.CANCELED,
                parent_id=created_event.id,
                activity_id=self.activity.id,
            )
            self.store.create(same_parent_task_event)

        assert_that(same_parent_task_event.parent_id, is_(created_event.id))
    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.graph.use("company_store")

        context = SessionContext(self.graph)
        context.recreate_all()
class TestEmployeeStore:

    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.company_store = self.graph.company_store
        self.employee_store = self.graph.employee_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.company = Company(
                name="name"
            ).create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_create(self):
        """
        Should be able to retrieve an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        retrieved_employee = Employee.retrieve(employee.id)
        assert_that(retrieved_employee.first, is_(equal_to("first")))
        assert_that(retrieved_employee.last, is_(equal_to("last")))

    def test_create_requires_foreign_key(self):
        """
        Should not be able to create an employee without a company.

        """
        employee = Employee(
            first="first",
            last="last",
        )

        assert_that(calling(employee.create), raises(ModelIntegrityError))

    def test_update(self):
        """
        Should be able to update an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            updated_employee = Employee(
                id=employee.id,
                first="Jane",
                last="Doe",
            ).update()
            assert_that(updated_employee.first, is_(equal_to("Jane")))
            assert_that(updated_employee.last, is_(equal_to("Doe")))
            assert_that(updated_employee.company_id, is_(equal_to(self.company.id)))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("Jane")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_update_with_diff(self):
        """
        Should be able to update an employee after creating it and get a diff.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            _, diff = Employee(
                id=employee.id,
                last="Doe",
            ).update_with_diff()
            assert_that(list(diff.keys()), contains_inanyorder("last", "updated_at"))
            assert_that(diff["last"].before, is_(equal_to("last")))
            assert_that(diff["last"].after, is_(equal_to("Doe")))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("first")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_update_not_found(self):
        """
        Should not be able to update an employee that does not exist.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            )
            assert_that(calling(employee.update), raises(ModelNotFoundError))

    def test_replace(self):
        """
        Should be able to replace an employee after creating it.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()

        with transaction():
            updated_employee = Employee(
                id=employee.id,
                first="Jane",
                last="Doe",
            ).replace()
            assert_that(updated_employee.first, is_(equal_to("Jane")))
            assert_that(updated_employee.last, is_(equal_to("Doe")))

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("Jane")))
            assert_that(retrieved_employee.last, is_(equal_to("Doe")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_replace_not_found(self):
        """
        Should be able to replace an employee that does not exist.

        """
        with transaction():
            employee = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).replace()

        with transaction():
            retrieved_employee = Employee.retrieve(employee.id)
            assert_that(retrieved_employee.first, is_(equal_to("first")))
            assert_that(retrieved_employee.last, is_(equal_to("last")))
            assert_that(Employee.count(), is_(equal_to(1)))

    def test_search_by_company(self):
        """
        Should be able to retrieve an employee after creating it.

        """
        with transaction():
            employee1 = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()
            employee2 = Employee(
                first="Jane",
                last="Doe",
                company_id=self.company.id,
            ).create()
            company2 = Company(
                name="other",
            ).create()
            employee3 = Employee(
                first="John",
                last="Doe",
                company_id=company2.id,
            ).create()

        assert_that(Employee.count(), is_(equal_to(3)))
        assert_that(
            [employee.last for employee in self.employee_store.search_by_company(self.company.id)],
            contains("Doe", "last"),
        )
        assert_that(
            [employee.id for employee in self.employee_store.search_by_company(self.company.id)],
            contains_inanyorder(employee1.id, employee2.id)
        )
        assert_that(
            [employee.id for employee in self.employee_store.search_by_company(company2.id)],
            contains_inanyorder(employee3.id)
        )

    def test_search_by_company_kwargs(self):
        """
        Should be able to filter searches using kwargs.

        """
        with transaction():
            employee1 = Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()
            employee2 = Employee(
                first="Jane",
                last="Doe",
                company_id=self.company.id,
            ).create()
            company2 = Company(
                name="other",
            ).create()
            employee3 = Employee(
                first="John",
                last="Doe",
                company_id=company2.id,
            ).create()

        assert_that(Employee.count(), is_(equal_to(3)))
        assert_that(
            [employee.id for employee in self.employee_store.search(company_id=self.company.id, offset=0, limit=10)],
            contains_inanyorder(employee1.id, employee2.id)
        )
        assert_that(self.employee_store.count(company_id=self.company.id, offset=0, limit=10), is_(equal_to(2)))
        assert_that(
            [employee.id for employee in self.employee_store.search(company_id=company2.id, offset=0, limit=10)],
            contains_inanyorder(employee3.id)
        )
        assert_that(self.employee_store.count(company_id=company2.id, offset=0, limit=10), is_(equal_to(1)))

    def test_search_first(self):
        """
        Should be able to search for the first item with matching criteria after creation.

        """
        with transaction():
            Employee(
                first="first",
                last="last",
                company_id=self.company.id,
            ).create()
            Employee(
                first="Jane",
                last="Doe",
                company_id=self.company.id,
            ).create()

        retrieved_real_employee = self.employee_store.search_first(first="Jane")
        assert_that(retrieved_real_employee.last, is_(equal_to("Doe")))
        retrieved_fake_employee = self.employee_store.search_first(first="Tarzan")
        assert_that(retrieved_fake_employee, is_(equal_to(None)))
示例#27
0
class TestCompany(object):

    def setup(self):
        self.graph = create_object_graph(name="example", testing=True, import_name="microcosm_postgres")
        self.company_store = self.graph.company_store
        self.employee_store = self.graph.employee_store

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

    def teardown(self):
        self.context.close()

    def test_create_retrieve_company(self):
        """
        Should be able to retrieve a company after creating it.

        """
        with transaction():
            company = Company(
                name="name",
                type=CompanyType.private,
            ).create()

        retrieved_company = Company.retrieve(company.id)
        assert_that(retrieved_company.name, is_(equal_to("name")))
        assert_that(retrieved_company.type, is_(equal_to(CompanyType.private)))

    def test_create_duplicate_company(self):
        """
        Should not be able to retrieve a company with a duplicate name.

        """
        with transaction():
            Company(name="name").create()

        company = Company(name="name")
        assert_that(calling(company.create), raises(DuplicateModelError))

    def test_create_delete_company(self):
        """
        Should not be able to retrieve a company after deleting it.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            company.delete()

        assert_that(calling(Company.retrieve).with_args(company.id), raises(ModelNotFoundError))

    def test_create_search_count_company(self):
        """
        Should be able to search and count companies after creation.

        """
        with transaction():
            company1 = Company(name="name1").create()
            company2 = Company(name="name2").create()

        assert_that(Company.count(), is_(equal_to(2)))
        assert_that([company.id for company in Company.search()], contains_inanyorder(company1.id, company2.id))

    def test_create_update_company(self):
        """
        Should be able to update a company after creating it.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            updated_company = Company(
                id=company.id,
                name="new_name",
            ).update()
            assert_that(updated_company.name, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_with_diff_company(self):
        """
        Should be able to update a company after creating it and get a diff.

        """
        with transaction():
            company = Company(name="name").create()

        with transaction():
            _, diff = Company(
                id=company.id,
                name="new_name",
            ).update_with_diff()
            assert_that(list(diff.keys()), contains_inanyorder("name", "updated_at"))
            assert_that(diff["name"].before, is_(equal_to("name")))
            assert_that(diff["name"].after, is_(equal_to("new_name")))

        with transaction():
            retrieved_company = Company.retrieve(company.id)
            assert_that(retrieved_company.name, is_(equal_to("new_name")))

    def test_create_update_duplicate_company(self):
        """
        Should be not able to update a company to a duplicate name.

        """
        with transaction():
            Company(name="name1").create()
            company = Company(name="name2").create()

        company.name = "name1"
        assert_that(calling(company.update), raises(DuplicateModelError))

    def test_delete_company_with_employees(self):
        """
        Should be not able to delete a company with employees.

        """
        with transaction():
            Company(name="name1").create()
            company = Company(name="name2").create()
            Employee(
                first="first",
                last="last",
                company_id=company.id,
            ).create()

        assert_that(calling(company.delete), raises(ReferencedModelError))
示例#28
0
class TestLast:
    def setup(self):
        self.graph = create_object_graph(
            "microcosm_eventsource",
            root_path=dirname(__file__),
            testing=True,
        )
        self.graph.use(
            "task_store",
            "task_event_store",
            "activity_store",
            "activity_event_store",
        )

        self.context = SessionContext(self.graph)
        self.context.recreate_all()
        self.context.open()

        with transaction():
            self.task = Task().create()
            self.created_event = TaskEvent(
                event_type=TaskEventType.CREATED,
                task_id=self.task.id,
            ).create()
            self.assigned_event = TaskEvent(
                assignee="Alice",
                event_type=TaskEventType.ASSIGNED,
                parent_id=self.created_event.id,
                task_id=self.task.id,
            ).create()
            self.started_event = TaskEvent(
                event_type=TaskEventType.STARTED,
                parent_id=self.assigned_event.id,
                task_id=self.task.id,
            ).create()
            self.reassigned_event = TaskEvent(
                assignee="Bob",
                event_type=TaskEventType.REASSIGNED,
                parent_id=self.started_event.id,
                task_id=self.task.id,
            ).create()
            self.reassigned_event = TaskEvent(
                event_type=TaskEventType.COMPLETED,
                parent_id=self.reassigned_event.id,
                task_id=self.task.id,
            ).create()

    def teardown(self):
        self.context.close()
        self.graph.postgres.dispose()

    def test_last(self):
        rows = self.context.session.query(
            TaskEvent.assignee,
            last.of(TaskEvent.assignee),
        ).order_by(TaskEvent.clock.desc(), ).all()

        assert_that(
            rows,
            contains(
                contains(None, "Bob"),
                contains("Bob", "Bob"),
                contains(None, "Alice"),
                contains("Alice", "Alice"),
                contains(None, None),
            ))

    def test_last_filter_by(self):
        rows = self.context.session.query(
            TaskEvent.assignee,
            last.of(
                TaskEvent.assignee,
                TaskEvent.event_type == TaskEventType.ASSIGNED,
            ),
        ).order_by(TaskEvent.clock.desc(), ).all()

        assert_that(
            rows,
            contains(
                contains(None, "Alice"),
                contains("Bob", "Alice"),
                contains(None, "Alice"),
                contains("Alice", "Alice"),
                contains(None, None),
            ))