fix(test): re-query user inside session to avoid detached Role error

Three tests in `test_timer_edit_own_time_entries.py` failed with
`sqlalchemy.exc.InvalidRequestError: Object '<Role at 0x...>' 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
This commit is contained in:
MacJediWizard
2026-05-14 16:48:43 -04:00
parent 8f18d3b624
commit c46fb360cb
+23 -9
View File
@@ -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/<id> 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(