diff --git a/OpenOversight/tests/conftest.py b/OpenOversight/tests/conftest.py index 0040e0b13..a9ef53d76 100644 --- a/OpenOversight/tests/conftest.py +++ b/OpenOversight/tests/conftest.py @@ -314,17 +314,16 @@ def db(app): @pytest.fixture(scope="function") def session(db): """Creates a new database session for a test.""" - connection = db.engine.connect() - transaction = connection.begin() - - session = scoped_session(session_factory=sessionmaker(bind=connection)) - db.session = session - - yield session - - transaction.rollback() - connection.close() - session.remove() + with db.engine.connect() as connection: + with connection.begin(): + session = scoped_session(session_factory=sessionmaker(bind=connection)) + db.session = session + + try: + yield session + finally: + session.remove() + connection.close() @pytest.fixture diff --git a/OpenOversight/tests/routes/test_descriptions.py b/OpenOversight/tests/routes/test_descriptions.py index b3bcb5d41..f19c5ec99 100644 --- a/OpenOversight/tests/routes/test_descriptions.py +++ b/OpenOversight/tests/routes/test_descriptions.py @@ -5,7 +5,7 @@ from flask import current_app, url_for from OpenOversight.app.main.forms import EditTextForm, TextForm -from OpenOversight.app.models.database import Description, Officer, User, db +from OpenOversight.app.models.database import Description, Officer, User from OpenOversight.app.utils.constants import ENCODING_UTF_8 from OpenOversight.tests.conftest import AC_DEPT from OpenOversight.tests.constants import ADMIN_USER_EMAIL @@ -134,8 +134,8 @@ def test_admins_can_edit_descriptions(mockdata, client, session): created_by=admin.id, last_updated_by=admin.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() form = EditTextForm( text_contents=new_description, @@ -175,8 +175,8 @@ def test_ac_can_edit_their_descriptions_in_their_department(mockdata, client, se created_by=user.id, last_updated_by=user.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() form = EditTextForm( text_contents=new_description, @@ -216,8 +216,8 @@ def test_ac_can_edit_others_descriptions(mockdata, client, session): created_by=user.id - 1, last_updated_by=user.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() form = EditTextForm( text_contents=new_description, @@ -257,8 +257,8 @@ def test_ac_cannot_edit_descriptions_not_in_their_department(mockdata, client, s created_at=original_date, last_updated_at=original_date, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() form = EditTextForm( text_contents=new_description, @@ -307,8 +307,8 @@ def test_acs_can_delete_their_descriptions_in_their_department( created_at=now, last_updated_at=now, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() description_id = description.id rv = client.post( url_for( @@ -338,8 +338,8 @@ def test_acs_cannot_delete_descriptions_not_in_their_department( created_at=now, last_updated_at=now, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() description_id = description.id rv = client.post( url_for( @@ -363,8 +363,8 @@ def test_acs_can_get_edit_form_for_their_dept(mockdata, client, session): text_contents="Hello", officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for( "main.description_api_edit", @@ -387,8 +387,8 @@ def test_acs_can_get_others_edit_form(mockdata, client, session): created_by=user.id - 1, last_updated_by=user.id - 1, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for( "main.description_api_edit", @@ -411,8 +411,8 @@ def test_acs_cannot_get_edit_form_for_their_non_dept(mockdata, client, session): text_contents="Hello", officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for( "main.description_api_edit", @@ -434,8 +434,8 @@ def test_users_can_see_descriptions(mockdata, client, session): text_contents=text_contents, officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -455,8 +455,8 @@ def test_admins_can_see_descriptions(mockdata, client, session): text_contents=text_contents, officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -475,8 +475,8 @@ def test_acs_can_see_descriptions_in_their_department(mockdata, client, session) text_contents=text_contents, officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -498,8 +498,8 @@ def test_acs_can_see_descriptions_not_in_their_department(mockdata, client, sess text_contents=text_contents, officer_id=officer.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -523,8 +523,8 @@ def test_anonymous_users_cannot_see_description_creators(mockdata, client, sessi created_by=ac.id, last_updated_by=ac.id, ) - db.session.add(description) - db.session.commit() + session.add(description) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), diff --git a/OpenOversight/tests/routes/test_notes.py b/OpenOversight/tests/routes/test_notes.py index 292072bab..09b5716e2 100644 --- a/OpenOversight/tests/routes/test_notes.py +++ b/OpenOversight/tests/routes/test_notes.py @@ -5,7 +5,7 @@ from flask import current_app, url_for from OpenOversight.app.main.forms import EditTextForm, TextForm -from OpenOversight.app.models.database import Department, Note, Officer, db +from OpenOversight.app.models.database import Department, Note, Officer from OpenOversight.app.models.database_cache import ( has_database_cache_entry, put_database_cache_entry, @@ -123,8 +123,8 @@ def test_admins_can_edit_notes(mockdata, client, session, faker): created_at=original_date, last_updated_at=original_date, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() form = EditTextForm( text_contents=new_note, @@ -159,8 +159,8 @@ def test_ac_can_edit_their_notes_in_their_department(mockdata, client, session, created_at=original_date, last_updated_at=original_date, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() form = EditTextForm( text_contents=new_note, @@ -193,8 +193,8 @@ def test_ac_can_edit_others_notes(mockdata, client, session): last_updated_at=original_date, last_updated_by=user.id - 1, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() form = EditTextForm( text_contents=new_note, @@ -230,8 +230,8 @@ def test_ac_cannot_edit_notes_not_in_their_department(mockdata, client, session) last_updated_at=original_date, last_updated_by=user.id, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() form = EditTextForm( text_contents=new_note, @@ -281,8 +281,8 @@ def test_acs_can_delete_their_notes_in_their_department(mockdata, client, sessio last_updated_at=now, last_updated_by=user.id, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.post( url_for("main.note_api_delete", officer_id=officer.id, obj_id=note.id), follow_redirects=True, @@ -309,8 +309,8 @@ def test_acs_cannot_delete_notes_not_in_their_department( last_updated_at=now, last_updated_by=2, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.post( url_for("main.note_api_delete", officer_id=officer.id, obj_id=note.id), follow_redirects=True, @@ -334,8 +334,8 @@ def test_acs_can_get_edit_form_for_their_dept(mockdata, client, session): last_updated_at=now, last_updated_by=user.id, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.note_api_edit", obj_id=note.id, officer_id=officer.id), follow_redirects=True, @@ -357,8 +357,8 @@ def test_acs_can_get_others_edit_form(mockdata, client, session): last_updated_at=now, last_updated_by=user.id - 1, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.note_api_edit", obj_id=note.id, officer_id=officer.id), follow_redirects=True, @@ -382,8 +382,8 @@ def test_acs_cannot_get_edit_form_for_their_non_dept(mockdata, client, session): last_updated_at=now, last_updated_by=2, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.note_api_edit", obj_id=note.id, officer_id=officer.id), follow_redirects=True, @@ -404,8 +404,8 @@ def test_users_cannot_see_notes(mockdata, client, session, faker): last_updated_at=now, last_updated_by=1, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -430,8 +430,8 @@ def test_admins_can_see_notes(mockdata, client, session): last_updated_at=now, last_updated_by=user.id, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -455,8 +455,8 @@ def test_acs_can_see_notes_in_their_department(mockdata, client, session): last_updated_at=now, last_updated_by=user.id, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, @@ -482,8 +482,8 @@ def test_acs_cannot_see_notes_not_in_their_department(mockdata, client, session) last_updated_at=now, last_updated_by=1, ) - db.session.add(note) - db.session.commit() + session.add(note) + session.commit() rv = client.get( url_for("main.officer_profile", officer_id=officer.id), follow_redirects=True, diff --git a/OpenOversight/tests/routes/test_user_api.py b/OpenOversight/tests/routes/test_user_api.py index 84f7cef09..823f5e6b8 100644 --- a/OpenOversight/tests/routes/test_user_api.py +++ b/OpenOversight/tests/routes/test_user_api.py @@ -4,7 +4,7 @@ from flask import current_app, url_for from OpenOversight.app.auth.forms import EditUserForm, LoginForm, RegistrationForm -from OpenOversight.app.models.database import User, db +from OpenOversight.app.models.database import User from OpenOversight.app.utils.constants import ENCODING_UTF_8 from OpenOversight.tests.conftest import AC_DEPT from OpenOversight.tests.constants import ( @@ -208,7 +208,7 @@ def test_admin_can_enable_user(mockdata, client, session): user = User.query.filter_by(email=GENERAL_USER_EMAIL).one() user.is_disabled = True - db.session.commit() + session.commit() user = session.get(User, user.id) assert user.is_disabled @@ -291,7 +291,7 @@ def test_admin_can_approve_user(mockdata, client, session): user = User.query.filter_by(email=GENERAL_USER_EMAIL).first() user.approved = False - db.session.commit() + session.commit() user = session.get(User, user.id) assert not user.approved @@ -344,7 +344,7 @@ def test_admin_approval_sends_confirmation_email( user = User.query.filter_by(is_administrator=False).first() user.approved = currently_approved user.confirmed = currently_confirmed - db.session.commit() + session.commit() user = session.get(User, user.id) assert user.approved == currently_approved diff --git a/OpenOversight/tests/test_functional.py b/OpenOversight/tests/test_functional.py index 39442fb8e..3abe4beb4 100644 --- a/OpenOversight/tests/test_functional.py +++ b/OpenOversight/tests/test_functional.py @@ -10,7 +10,7 @@ from selenium.webdriver.support.ui import WebDriverWait from sqlalchemy.sql.expression import func -from OpenOversight.app.models.database import Department, Incident, Officer, Unit, db +from OpenOversight.app.models.database import Department, Incident, Officer, Unit from OpenOversight.app.utils.constants import FILE_TYPE_HTML, KEY_OFFICERS_PER_PAGE from OpenOversight.tests.conftest import AC_DEPT from OpenOversight.tests.constants import ADMIN_USER_EMAIL @@ -273,13 +273,13 @@ def test_click_to_read_more_hides_the_read_more_button(mockdata, browser, server assert not buttonRow.is_displayed() -def test_officer_form_has_units_alpha_sorted(mockdata, browser, server_port): +def test_officer_form_has_units_alpha_sorted(mockdata, browser, server_port, session): login_admin(browser, server_port) # get the units from the DB in the sort we expect db_units_sorted = [ x.description - for x in db.session.query(Unit).order_by(Unit.description.asc()).all() + for x in session.query(Unit).order_by(Unit.description.asc()).all() ] # the Select tag in the interface has a 'None' value at the start db_units_sorted.insert(0, "None") @@ -298,13 +298,13 @@ def test_officer_form_has_units_alpha_sorted(mockdata, browser, server_port): def test_edit_officer_form_coerces_none_race_or_gender_to_not_sure( - mockdata, browser, server_port + mockdata, browser, server_port, session ): # Set NULL race and gender for officer 1 - db.session.execute( + session.execute( Officer.__table__.update().where(Officer.id == 1).values(race=None, gender=None) ) - db.session.commit() + session.commit() login_admin(browser, server_port) diff --git a/OpenOversight/tests/test_models.py b/OpenOversight/tests/test_models.py index 98d0916ce..35094a80f 100644 --- a/OpenOversight/tests/test_models.py +++ b/OpenOversight/tests/test_models.py @@ -20,7 +20,6 @@ Salary, Unit, User, - db, ) from OpenOversight.app.utils.choices import STATE_CHOICES from OpenOversight.tests.conftest import SPRINGFIELD_PD @@ -186,10 +185,10 @@ def test_password_set_success(mockdata): assert user.password_hash is not None -def test_password_setter_regenerates_uuid(mockdata): +def test_password_setter_regenerates_uuid(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() initial_uuid = user.uuid user.password = "pork belly" assert user.uuid is not None @@ -212,21 +211,21 @@ def test_password_salting(mockdata): assert user1.password_hash != user2.password_hash -def test__uuid_default(mockdata): +def test__uuid_default(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() assert user._uuid is not None -def test__uuid_uniqueness_constraint(mockdata): +def test__uuid_uniqueness_constraint(mockdata, session): user1 = User(password="bacon") user2 = User(password="vegan bacon") user2._uuid = user1._uuid - db.session.add(user1) - db.session.add(user2) + session.add(user1) + session.add(user2) with raises(IntegrityError): - db.session.commit() + session.commit() def test_uuid(mockdata): @@ -240,37 +239,37 @@ def test_uuid_setter(mockdata): User(uuid="8e9f1393-99b8-466c-80ce-8a56a7d9849d") -def test_valid_confirmation_token(mockdata): +def test_valid_confirmation_token(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_confirmation_token() assert user.confirm(token) is True -def test_invalid_confirmation_token(mockdata): +def test_invalid_confirmation_token(mockdata, session): user1 = User(password="bacon") user2 = User(password="bacon") - db.session.add(user1) - db.session.add(user2) - db.session.commit() + session.add(user1) + session.add(user2) + session.commit() token = user1.generate_confirmation_token() assert user2.confirm(token) is False -def test_expired_confirmation_token(mockdata): +def test_expired_confirmation_token(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_confirmation_token(1) time.sleep(2) assert user.confirm(token) is False -def test_valid_reset_token(mockdata): +def test_valid_reset_token(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_reset_token() pre_reset_uuid = user.uuid assert user.reset_password(token, "vegan bacon") is True @@ -278,30 +277,30 @@ def test_valid_reset_token(mockdata): assert user.uuid != pre_reset_uuid -def test_invalid_reset_token(mockdata): +def test_invalid_reset_token(mockdata, session): user1 = User(password="bacon") user2 = User(password="vegan bacon") - db.session.add(user1) - db.session.add(user2) - db.session.commit() + session.add(user1) + session.add(user2) + session.commit() token = user1.generate_reset_token() assert user2.reset_password(token, "tempeh") is False assert user2.verify_password("vegan bacon") is True -def test_expired_reset_token(mockdata): +def test_expired_reset_token(mockdata, session): user = User(password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_reset_token(expiration=-1) assert user.reset_password(token, "tempeh") is False assert user.verify_password("bacon") is True -def test_valid_email_change_token(mockdata): +def test_valid_email_change_token(mockdata, session): user = User(email="brian@example.com", password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() pre_reset_uuid = user.uuid token = user.generate_email_change_token("lucy@example.org") assert user.change_email(token) is True @@ -309,47 +308,47 @@ def test_valid_email_change_token(mockdata): assert user.uuid != pre_reset_uuid -def test_email_change_token_no_email(mockdata): +def test_email_change_token_no_email(mockdata, session): user = User(email="brian@example.com", password="bacon") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_email_change_token(None) assert user.change_email(token) is False assert user.email == "brian@example.com" -def test_invalid_email_change_token(mockdata): +def test_invalid_email_change_token(mockdata, session): user1 = User(email="jen@example.com", password="cat") user2 = User(email="freddy@example.com", password="dog") - db.session.add(user1) - db.session.add(user2) - db.session.commit() + session.add(user1) + session.add(user2) + session.commit() token = user1.generate_email_change_token("mason@example.net") assert user2.change_email(token) is False assert user2.email == "freddy@example.com" -def test_expired_email_change_token(mockdata): +def test_expired_email_change_token(mockdata, session): user = User(email="jen@example.com", password="cat") - db.session.add(user) - db.session.commit() + session.add(user) + session.commit() token = user.generate_email_change_token("mason@example.net", expiration=-1) assert user.change_email(token) is False assert user.email == "jen@example.com" -def test_duplicate_email_change_token(mockdata): +def test_duplicate_email_change_token(mockdata, session): user1 = User(email="alice@example.com", password="cat") user2 = User(email="bob@example.org", password="dog") - db.session.add(user1) - db.session.add(user2) - db.session.commit() + session.add(user1) + session.add(user2) + session.commit() token = user2.generate_email_change_token("alice@example.com") assert user2.change_email(token) is False assert user2.email == "bob@example.org" -def test_area_coordinator_with_dept_is_valid(mockdata): +def test_area_coordinator_with_dept_is_valid(mockdata, session): user1 = User( email="alice@example.com", username="me", @@ -357,8 +356,8 @@ def test_area_coordinator_with_dept_is_valid(mockdata): is_area_coordinator=True, ac_department_id=1, ) - db.session.add(user1) - db.session.commit() + session.add(user1) + session.commit() assert user1.is_area_coordinator is True assert user1.ac_department_id == 1 @@ -433,7 +432,7 @@ def test_location_repr(faker): ) -def test_locations_can_be_saved_with_valid_zip_codes(mockdata): +def test_locations_can_be_saved_with_valid_zip_codes(mockdata, session): zip_code = "03456" city = "Cambridge" lo = Location( @@ -444,8 +443,8 @@ def test_locations_can_be_saved_with_valid_zip_codes(mockdata): state="MA", zip_code=zip_code, ) - db.session.add(lo) - db.session.commit() + session.add(lo) + session.commit() saved = Location.query.filter_by(zip_code=zip_code, city=city) assert saved is not None @@ -462,7 +461,7 @@ def test_locations_must_have_valid_states(mockdata): ) -def test_locations_can_be_saved_with_valid_states(mockdata): +def test_locations_can_be_saved_with_valid_states(mockdata, session): state = "AZ" city = "Cambridge" lo = Location( @@ -474,8 +473,8 @@ def test_locations_can_be_saved_with_valid_states(mockdata): zip_code="54340", ) - db.session.add(lo) - db.session.commit() + session.add(lo) + session.commit() saved = Location.query.filter_by(city=city, state=state).first() assert saved is not None @@ -485,7 +484,7 @@ def test_license_plates_must_have_valid_states(mockdata): LicensePlate(number="603EEE", state="JK") -def test_license_plates_can_be_saved_with_valid_states(mockdata): +def test_license_plates_can_be_saved_with_valid_states(mockdata, session): state = "AZ" number = "603RRR" lp = LicensePlate( @@ -493,8 +492,8 @@ def test_license_plates_can_be_saved_with_valid_states(mockdata): state=state, ) - db.session.add(lp) - db.session.commit() + session.add(lp) + session.commit() saved = LicensePlate.query.filter_by(number=number, state=state).first() assert saved is not None @@ -505,16 +504,16 @@ def test_links_must_have_valid_urls(mockdata, faker): Link(link_type="video", url=bad_url) -def test_links_can_be_saved_with_valid_urls(mockdata, faker): +def test_links_can_be_saved_with_valid_urls(faker, mockdata, session): good_url = faker.url() li = Link(link_type="video", url=good_url) - db.session.add(li) - db.session.commit() + session.add(li) + session.commit() saved = Link.query.filter_by(url=good_url).first() assert saved is not None -def test_incident_m2m_officers(mockdata): +def test_incident_m2m_officers(mockdata, session): incident = Incident.query.first() officer = Officer( first_name="Test", @@ -525,39 +524,39 @@ def test_incident_m2m_officers(mockdata): birth_year=1990, ) incident.officers.append(officer) - db.session.add(incident) - db.session.add(officer) - db.session.commit() + session.add(incident) + session.add(officer) + session.commit() assert officer in incident.officers assert incident in officer.incidents -def test_incident_m2m_links(mockdata, faker): +def test_incident_m2m_links(faker, mockdata, session): incident = Incident.query.first() link = Link(link_type="video", url=faker.url()) incident.links.append(link) - db.session.add(incident) - db.session.add(link) - db.session.commit() + session.add(incident) + session.add(link) + session.commit() assert link in incident.links assert incident in link.incidents -def test_incident_m2m_license_plates(mockdata): +def test_incident_m2m_license_plates(mockdata, session): incident = Incident.query.first() license_plate = LicensePlate( number="W23F43", state="DC", ) incident.license_plates.append(license_plate) - db.session.add(incident) - db.session.add(license_plate) - db.session.commit() + session.add(incident) + session.add(license_plate) + session.commit() assert license_plate in incident.license_plates assert incident in license_plate.incidents -def test_images_added_with_user_id(mockdata, faker): +def test_images_added_with_user_id(faker, mockdata, session): user_id = 1 new_image = Image( filepath=faker.url(), @@ -567,8 +566,8 @@ def test_images_added_with_user_id(mockdata, faker): taken_at=datetime.datetime.now(), created_by=user_id, ) - db.session.add(new_image) - db.session.commit() + session.add(new_image) + session.commit() saved = Image.query.filter_by(created_by=user_id).first() assert saved is not None diff --git a/database/README.md b/database/README.md index 9b38fe18f..ae2f9d386 100644 --- a/database/README.md +++ b/database/README.md @@ -4,4 +4,4 @@ Running `make dev` in the docker environment will create and persist the database. ## Database Diagram -![](database_relationships.Sept2023.png) +![](database_relationships.July2024.png) diff --git a/database/database_relationships.July2024.png b/database/database_relationships.July2024.png new file mode 100644 index 000000000..eaa1ef41c Binary files /dev/null and b/database/database_relationships.July2024.png differ diff --git a/database/database_relationships.Sept2023.png b/database/database_relationships.Sept2023.png deleted file mode 100644 index d33c38c85..000000000 Binary files a/database/database_relationships.Sept2023.png and /dev/null differ