Re-factor the ORM tests to use randomized fake data
- create `*Factory` classes with fakerboy and faker that generate randomized instances of the ORM models - add new pytest marker: "db" are the integration tests involving the database whereas "e2e" will be all other integration tests - streamline the docstrings in the ORM models
This commit is contained in:
parent
416a58f9dc
commit
78dba23d5d
19 changed files with 1092 additions and 721 deletions
|
|
@ -1,23 +1,23 @@
|
|||
"""Utils for testing the ORM layer."""
|
||||
|
||||
import datetime
|
||||
|
||||
import pytest
|
||||
from alembic import command as migrations_cmd
|
||||
from alembic import config as migrations_config
|
||||
from sqlalchemy import orm
|
||||
|
||||
from tests.db import fake_data
|
||||
from urban_meal_delivery import config
|
||||
from urban_meal_delivery import db
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', params=['all_at_once', 'sequentially'])
|
||||
def db_engine(request):
|
||||
def db_connection(request):
|
||||
"""Create all tables given the ORM models.
|
||||
|
||||
The tables are put into a distinct PostgreSQL schema
|
||||
that is removed after all tests are over.
|
||||
|
||||
The engine used to do that is yielded.
|
||||
The database connection used to do that is yielded.
|
||||
|
||||
There are two modes for this fixture:
|
||||
|
||||
|
|
@ -27,38 +27,40 @@ def db_engine(request):
|
|||
This ensures that Alembic's migration files are consistent.
|
||||
"""
|
||||
engine = db.make_engine()
|
||||
connection = engine.connect()
|
||||
|
||||
if request.param == 'all_at_once':
|
||||
engine.execute(f'CREATE SCHEMA {config.CLEAN_SCHEMA};')
|
||||
db.Base.metadata.create_all(engine)
|
||||
db.Base.metadata.create_all(connection)
|
||||
else:
|
||||
cfg = migrations_config.Config('alembic.ini')
|
||||
migrations_cmd.upgrade(cfg, 'head')
|
||||
|
||||
try:
|
||||
yield engine
|
||||
yield connection
|
||||
|
||||
finally:
|
||||
engine.execute(f'DROP SCHEMA {config.CLEAN_SCHEMA} CASCADE;')
|
||||
connection.execute(f'DROP SCHEMA {config.CLEAN_SCHEMA} CASCADE;')
|
||||
|
||||
if request.param == 'sequentially':
|
||||
tmp_alembic_version = f'{config.ALEMBIC_TABLE}_{config.CLEAN_SCHEMA}'
|
||||
engine.execute(
|
||||
connection.execute(
|
||||
f'DROP TABLE {config.ALEMBIC_TABLE_SCHEMA}.{tmp_alembic_version};',
|
||||
)
|
||||
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def db_session(db_engine):
|
||||
def db_session(db_connection):
|
||||
"""A SQLAlchemy session that rolls back everything after a test case."""
|
||||
connection = db_engine.connect()
|
||||
# Begin the outer most transaction
|
||||
# that is rolled back at the end of the test.
|
||||
transaction = connection.begin()
|
||||
transaction = db_connection.begin()
|
||||
# Create a session bound on the same connection as the transaction.
|
||||
# Using any other session would not work.
|
||||
Session = db.make_session_factory() # noqa:N806
|
||||
session = Session(bind=connection)
|
||||
session_factory = orm.sessionmaker()
|
||||
session = session_factory(bind=db_connection)
|
||||
|
||||
try:
|
||||
yield session
|
||||
|
|
@ -66,198 +68,20 @@ def db_session(db_engine):
|
|||
finally:
|
||||
session.close()
|
||||
transaction.rollback()
|
||||
connection.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def address_data():
|
||||
"""The data for an Address object in Paris."""
|
||||
return {
|
||||
'id': 1,
|
||||
'_primary_id': 1, # => "itself"
|
||||
'created_at': datetime.datetime(2020, 1, 2, 3, 4, 5),
|
||||
'place_id': 'ChIJxSr71vZt5kcRoFHY4caCCxw',
|
||||
'latitude': 48.85313,
|
||||
'longitude': 2.37461,
|
||||
'_city_id': 1,
|
||||
'city_name': 'St. German',
|
||||
'zip_code': '75011',
|
||||
'street': '42 Rue De Charonne',
|
||||
'floor': None,
|
||||
}
|
||||
# Import the fixtures from the `fake_data` sub-package.
|
||||
|
||||
make_address = fake_data.make_address
|
||||
make_courier = fake_data.make_courier
|
||||
make_customer = fake_data.make_customer
|
||||
make_order = fake_data.make_order
|
||||
make_restaurant = fake_data.make_restaurant
|
||||
|
||||
@pytest.fixture
|
||||
def address(address_data, city):
|
||||
"""An Address object."""
|
||||
address = db.Address(**address_data)
|
||||
address.city = city
|
||||
return address
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def address2_data():
|
||||
"""The data for an Address object in Paris."""
|
||||
return {
|
||||
'id': 2,
|
||||
'_primary_id': 2, # => "itself"
|
||||
'created_at': datetime.datetime(2020, 1, 2, 4, 5, 6),
|
||||
'place_id': 'ChIJs-9a6QZy5kcRY8Wwk9Ywzl8',
|
||||
'latitude': 48.852196,
|
||||
'longitude': 2.373937,
|
||||
'_city_id': 1,
|
||||
'city_name': 'Paris',
|
||||
'zip_code': '75011',
|
||||
'street': 'Rue De Charonne 3',
|
||||
'floor': 2,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def address2(address2_data, city):
|
||||
"""An Address object."""
|
||||
address2 = db.Address(**address2_data)
|
||||
address2.city = city
|
||||
return address2
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def city_data():
|
||||
"""The data for the City object modeling Paris."""
|
||||
return {
|
||||
'id': 1,
|
||||
'name': 'Paris',
|
||||
'kml': "<?xml version='1.0' encoding='UTF-8'?> ...",
|
||||
'_center_latitude': 48.856614,
|
||||
'_center_longitude': 2.3522219,
|
||||
'_northeast_latitude': 48.9021449,
|
||||
'_northeast_longitude': 2.4699208,
|
||||
'_southwest_latitude': 48.815573,
|
||||
'_southwest_longitude': 2.225193,
|
||||
'initial_zoom': 12,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def city(city_data):
|
||||
"""A City object."""
|
||||
return db.City(**city_data)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def courier_data():
|
||||
"""The data for a Courier object."""
|
||||
return {
|
||||
'id': 1,
|
||||
'created_at': datetime.datetime(2020, 1, 2, 3, 4, 5),
|
||||
'vehicle': 'bicycle',
|
||||
'historic_speed': 7.89,
|
||||
'capacity': 100,
|
||||
'pay_per_hour': 750,
|
||||
'pay_per_order': 200,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def courier(courier_data):
|
||||
"""A Courier object."""
|
||||
return db.Courier(**courier_data)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def customer_data():
|
||||
"""The data for the Customer object."""
|
||||
return {'id': 1}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def customer(customer_data):
|
||||
"""A Customer object."""
|
||||
return db.Customer(**customer_data)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def order_data():
|
||||
"""The data for an ad-hoc Order object."""
|
||||
return {
|
||||
'id': 1,
|
||||
'_delivery_id': 1,
|
||||
'_customer_id': 1,
|
||||
'placed_at': datetime.datetime(2020, 1, 2, 11, 55, 11),
|
||||
'ad_hoc': True,
|
||||
'scheduled_delivery_at': None,
|
||||
'scheduled_delivery_at_corrected': None,
|
||||
'first_estimated_delivery_at': datetime.datetime(2020, 1, 2, 12, 35, 0),
|
||||
'cancelled': False,
|
||||
'cancelled_at': None,
|
||||
'cancelled_at_corrected': None,
|
||||
'sub_total': 2000,
|
||||
'delivery_fee': 250,
|
||||
'total': 2250,
|
||||
'_restaurant_id': 1,
|
||||
'restaurant_notified_at': datetime.datetime(2020, 1, 2, 12, 5, 5),
|
||||
'restaurant_notified_at_corrected': False,
|
||||
'restaurant_confirmed_at': datetime.datetime(2020, 1, 2, 12, 5, 25),
|
||||
'restaurant_confirmed_at_corrected': False,
|
||||
'estimated_prep_duration': 900,
|
||||
'estimated_prep_duration_corrected': False,
|
||||
'estimated_prep_buffer': 480,
|
||||
'_courier_id': 1,
|
||||
'dispatch_at': datetime.datetime(2020, 1, 2, 12, 5, 1),
|
||||
'dispatch_at_corrected': False,
|
||||
'courier_notified_at': datetime.datetime(2020, 1, 2, 12, 6, 2),
|
||||
'courier_notified_at_corrected': False,
|
||||
'courier_accepted_at': datetime.datetime(2020, 1, 2, 12, 6, 17),
|
||||
'courier_accepted_at_corrected': False,
|
||||
'utilization': 50,
|
||||
'_pickup_address_id': 1,
|
||||
'reached_pickup_at': datetime.datetime(2020, 1, 2, 12, 16, 21),
|
||||
'pickup_at': datetime.datetime(2020, 1, 2, 12, 18, 1),
|
||||
'pickup_at_corrected': False,
|
||||
'pickup_not_confirmed': False,
|
||||
'left_pickup_at': datetime.datetime(2020, 1, 2, 12, 19, 45),
|
||||
'left_pickup_at_corrected': False,
|
||||
'_delivery_address_id': 2,
|
||||
'reached_delivery_at': datetime.datetime(2020, 1, 2, 12, 27, 33),
|
||||
'delivery_at': datetime.datetime(2020, 1, 2, 12, 29, 55),
|
||||
'delivery_at_corrected': False,
|
||||
'delivery_not_confirmed': False,
|
||||
'_courier_waited_at_delivery': False,
|
||||
'logged_delivery_distance': 500,
|
||||
'logged_avg_speed': 7.89,
|
||||
'logged_avg_speed_distance': 490,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def order( # noqa:WPS211 pylint:disable=too-many-arguments
|
||||
order_data, customer, restaurant, courier, address, address2,
|
||||
):
|
||||
"""An Order object."""
|
||||
order = db.Order(**order_data)
|
||||
order.customer = customer
|
||||
order.restaurant = restaurant
|
||||
order.courier = courier
|
||||
order.pickup_address = address
|
||||
order.delivery_address = address2
|
||||
return order
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restaurant_data():
|
||||
"""The data for the Restaurant object."""
|
||||
return {
|
||||
'id': 1,
|
||||
'created_at': datetime.datetime(2020, 1, 2, 3, 4, 5),
|
||||
'name': 'Vevay',
|
||||
'_address_id': 1,
|
||||
'estimated_prep_duration': 1000,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def restaurant(restaurant_data, address):
|
||||
"""A Restaurant object."""
|
||||
restaurant = db.Restaurant(**restaurant_data)
|
||||
restaurant.address = address
|
||||
return restaurant
|
||||
address = fake_data.address
|
||||
city = fake_data.city
|
||||
city_data = fake_data.city_data
|
||||
courier = fake_data.courier
|
||||
customer = fake_data.customer
|
||||
order = fake_data.order
|
||||
restaurant = fake_data.restaurant
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue