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 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)))
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))
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()))
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), )
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))
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)))
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", ))
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()))
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))
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 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, ), ))
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)))
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), )
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)))
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 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), ), ), )
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)))
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))
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)))
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))
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), ))