|
1 | 1 | # ruff: noqa: E402 |
2 | 2 |
|
3 | | -import pytest |
4 | 3 | from unittest.mock import patch |
5 | 4 |
|
| 5 | +import pytest |
| 6 | +from sqlalchemy.exc import IntegrityError |
| 7 | + |
6 | 8 | arq = pytest.importorskip("arq") |
7 | 9 | cdot = pytest.importorskip("cdot") |
8 | 10 | fastapi = pytest.importorskip("fastapi") |
|
11 | 13 | from mavedb.models.enums.user_role import UserRole |
12 | 14 | from mavedb.models.user import User |
13 | 15 | from tests.helpers.constants import ADMIN_USER, ADMIN_USER_DECODED_JWT, TEST_USER, TEST_USER_DECODED_JWT |
14 | | - |
15 | 16 | from tests.helpers.util.access_key import create_api_key_for_user |
16 | 17 | from tests.helpers.util.user import mark_user_inactive |
17 | 18 |
|
@@ -121,3 +122,51 @@ async def test_get_current_user_user_extraneous_roles(session, setup_lib_db): |
121 | 122 |
|
122 | 123 | assert user_data.user.username == TEST_USER["username"] |
123 | 124 | assert user_data.active_roles == [] |
| 125 | + |
| 126 | + |
| 127 | +@pytest.mark.asyncio |
| 128 | +async def test_get_current_user_concurrent_first_login_integrity_error_returns_existing_user(session, setup_lib_db): |
| 129 | + """ |
| 130 | + Simulate two servers racing on first login: the commit raises IntegrityError because a |
| 131 | + concurrent request already inserted the row. The handler should roll back and return the |
| 132 | + existing user rather than surfacing the error. |
| 133 | + """ |
| 134 | + new_user_jwt = { |
| 135 | + "sub": "9999-0000-0000-9999", |
| 136 | + "given_name": "Race", |
| 137 | + "family_name": "Condition", |
| 138 | + } |
| 139 | + |
| 140 | + # Insert the user as if a concurrent request already committed it. |
| 141 | + pre_existing = User( |
| 142 | + username=new_user_jwt["sub"], |
| 143 | + first_name=new_user_jwt["given_name"], |
| 144 | + last_name=new_user_jwt["family_name"], |
| 145 | + is_active=True, |
| 146 | + is_first_login=True, |
| 147 | + ) |
| 148 | + session.add(pre_existing) |
| 149 | + session.commit() |
| 150 | + |
| 151 | + # Wrap the real session so we can intercept the first commit call and raise IntegrityError, |
| 152 | + # letting subsequent calls (rollback, refresh, etc.) pass through to the real session. |
| 153 | + original_commit = session.commit |
| 154 | + commit_calls = [] |
| 155 | + |
| 156 | + def fake_commit(): |
| 157 | + commit_calls.append(1) |
| 158 | + if len(commit_calls) == 1: |
| 159 | + raise IntegrityError(statement=None, params=None, orig=Exception("duplicate key")) |
| 160 | + return original_commit() |
| 161 | + |
| 162 | + session.commit = fake_commit |
| 163 | + |
| 164 | + with patch("mavedb.lib.authentication.fetch_orcid_user_email", return_value=None): |
| 165 | + user_data = await get_current_user(None, new_user_jwt, session, None) |
| 166 | + |
| 167 | + assert user_data is not None |
| 168 | + assert user_data.user.username == new_user_jwt["sub"] |
| 169 | + |
| 170 | + # Only one user record should exist in the database. |
| 171 | + users = session.query(User).filter(User.username == new_user_jwt["sub"]).all() |
| 172 | + assert len(users) == 1 |
0 commit comments