From c46fb360cb70d56538d3a2e25dfcf1d342c7568d Mon Sep 17 00:00:00 2001 From: MacJediWizard <99237059+MacJediWizard@users.noreply.github.com> Date: Thu, 14 May 2026 16:48:43 -0400 Subject: [PATCH] fix(test): re-query user inside session to avoid detached Role error MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three tests in `test_timer_edit_own_time_entries.py` failed with `sqlalchemy.exc.InvalidRequestError: Object '' is not attached to a Session` when the suite ran after v5.5.6's fixture refresh. Root cause: the `user` fixture commits the user and calls `db.session.refresh(user)`, leaving the instance bound to the fixture's scoped session. Each test then enters a fresh `with app.app_context():` block before calling `_ensure_edit_own_permission(user)`. Flask-SQLAlchemy's scoped session in the new context is a *different* session than the one that owns `user`, so when `user.add_role(role)` tries to associate a freshly loaded `Role` with the cross-session `user`, the resulting commit detaches the role and the next attribute access fails. Fix: have `_ensure_edit_own_permission` take a `user_id` instead of the ORM instance and re-query the user from the active session (`User.query.get(user_id)`). All three objects (user, role, permission) now live in the same identity map. Drop the trailing `db.session.refresh(user)` — route handlers re-load the user via their own request-scoped sessions and see the persisted association. Test plan - pytest tests/test_timer_edit_own_time_entries.py --- tests/test_timer_edit_own_time_entries.py | 32 ++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/tests/test_timer_edit_own_time_entries.py b/tests/test_timer_edit_own_time_entries.py index 04deb984..a774229c 100644 --- a/tests/test_timer_edit_own_time_entries.py +++ b/tests/test_timer_edit_own_time_entries.py @@ -5,10 +5,18 @@ from datetime import datetime import pytest from app import db -from app.models import Permission, Role, TimeEntry +from app.models import Permission, Role, TimeEntry, User -def _ensure_edit_own_permission(user): +def _ensure_edit_own_permission(user_id): + """Grant the edit_own_time_entries permission to the user with the given id. + + The user id is passed instead of the ORM instance so that every object + touched here (user, role, permission) is loaded inside the currently + active session. This avoids cross-session "object is not attached" + errors when the calling test enters a new app_context after the user + fixture has already committed and detached the user instance. + """ perm = Permission.query.filter_by(name="edit_own_time_entries").first() if not perm: perm = Permission( @@ -24,18 +32,20 @@ def _ensure_edit_own_permission(user): db.session.add(role) db.session.flush() role.add_permission(perm) + user = User.query.get(user_id) if role not in user.roles: user.add_role(role) db.session.commit() - db.session.refresh(user) @pytest.mark.integration @pytest.mark.routes -def test_edit_timer_page_shows_schedule_fields_with_edit_own_permission(app, authenticated_client, user, project): +def test_edit_timer_page_shows_schedule_fields_with_edit_own_permission( + app, authenticated_client, user, project +): """GET /timer/edit shows date/time inputs when user has edit_own_time_entries.""" with app.app_context(): - _ensure_edit_own_permission(user) + _ensure_edit_own_permission(user.id) start = datetime(2020, 6, 1, 9, 0, 0) end = datetime(2020, 6, 1, 11, 0, 0) entry = TimeEntry( @@ -58,10 +68,12 @@ def test_edit_timer_page_shows_schedule_fields_with_edit_own_permission(app, aut @pytest.mark.integration @pytest.mark.routes -def test_edit_timer_post_updates_times_with_edit_own_permission(app, authenticated_client, user, project): +def test_edit_timer_post_updates_times_with_edit_own_permission( + app, authenticated_client, user, project +): """POST /timer/edit applies new start/end when user has edit_own_time_entries.""" with app.app_context(): - _ensure_edit_own_permission(user) + _ensure_edit_own_permission(user.id) start = datetime(2020, 6, 1, 9, 0, 0) end = datetime(2020, 6, 1, 11, 0, 0) entry = TimeEntry( @@ -104,10 +116,12 @@ def test_edit_timer_post_updates_times_with_edit_own_permission(app, authenticat @pytest.mark.integration @pytest.mark.routes -def test_api_entry_put_updates_times_with_edit_own_permission(app, authenticated_client, user, project): +def test_api_entry_put_updates_times_with_edit_own_permission( + app, authenticated_client, user, project +): """PUT /api/entry/ accepts start/end for own entry with edit_own_time_entries.""" with app.app_context(): - _ensure_edit_own_permission(user) + _ensure_edit_own_permission(user.id) start = datetime(2020, 6, 2, 9, 0, 0) end = datetime(2020, 6, 2, 10, 0, 0) entry = TimeEntry(