Skip to content

Commit 262d355

Browse files
committed
FIX: New Users Created Twice
Fixes a bug where new users were added to the database twice. Adds test cases for lib module authentication.py which would have prevented this bug.
1 parent c1aea6c commit 262d355

File tree

5 files changed

+214
-5
lines changed

5 files changed

+214
-5
lines changed

src/mavedb/lib/authentication.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,8 @@ async def get_current_user(
158158
email=email,
159159
is_first_login=True,
160160
)
161-
logger.info(f"Creating new user with username {user.username}")
162161

163-
db.add(user)
164-
db.commit()
165-
db.refresh(user)
162+
logger.info(f"Creating new user with username {user.username}")
166163

167164
elif not user.is_active:
168165
return None

tests/helpers/constants.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
"is_first_login": True,
2222
}
2323

24+
TEST_USER_DECODED_JWT = {
25+
"sub": TEST_USER["username"],
26+
"given_name": TEST_USER["first_name"],
27+
"family_name": TEST_USER["last_name"],
28+
}
29+
2430
EXTRA_USER = {
2531
"username": "1234-5678-8765-4321",
2632
"first_name": "Extra",
@@ -32,6 +38,12 @@
3238
"is_first_login": True,
3339
}
3440

41+
EXTRA_USER_DECODED_JWT = {
42+
"sub": EXTRA_USER["username"],
43+
"given_name": EXTRA_USER["first_name"],
44+
"family_name": EXTRA_USER["last_name"],
45+
}
46+
3547
ADMIN_USER = {
3648
"username": "9999-9999-9999-9999",
3749
"first_name": "Admin",
@@ -43,6 +55,12 @@
4355
"is_first_login": True,
4456
}
4557

58+
ADMIN_USER_DECODED_JWT = {
59+
"sub": ADMIN_USER["username"],
60+
"given_name": ADMIN_USER["first_name"],
61+
"family_name": ADMIN_USER["last_name"],
62+
}
63+
4664
TEST_EXPERIMENT = {
4765
"title": "Test Title",
4866
"short_description": "Test experiment",

tests/helpers/util.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,3 +175,26 @@ def publish_score_set(client, score_set_urn):
175175
response_data = response.json()
176176
jsonschema.validate(instance=response_data, schema=ScoreSet.schema())
177177
return response_data
178+
179+
180+
def create_api_key_for_current_user(client):
181+
response = client.post("api/v1/users/me/access-keys")
182+
assert response.status_code == 200
183+
return response.json()["keyId"]
184+
185+
186+
def create_admin_key_for_current_user(client):
187+
response = client.post("api/v1/users/me/access-keys/admin")
188+
assert response.status_code == 200
189+
return response.json()["keyId"]
190+
191+
192+
def mark_user_inactive(session, username):
193+
user = session.query(User).where(User.username == username).one()
194+
user.is_active = False
195+
196+
session.add(user)
197+
session.commit()
198+
session.refresh(user)
199+
200+
return user

tests/lib/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from mavedb.models.license import License
44
from mavedb.models.taxonomy import Taxonomy
55
from mavedb.models.user import User
6+
from mavedb.models.role import Role
7+
from mavedb.models.enums.user_role import UserRole
68

7-
from tests.helpers.constants import EXTRA_USER, TEST_LICENSE, TEST_TAXONOMY, TEST_USER
9+
from tests.helpers.constants import ADMIN_USER, EXTRA_USER, TEST_LICENSE, TEST_TAXONOMY, TEST_USER
810

911

1012
@pytest.fixture
@@ -16,6 +18,7 @@ def setup_lib_db(session):
1618
db = session
1719
db.add(User(**TEST_USER))
1820
db.add(User(**EXTRA_USER))
21+
db.add(User(**ADMIN_USER, role_objs=[Role(name=UserRole.admin)]))
1922
db.add(Taxonomy(**TEST_TAXONOMY))
2023
db.add(License(**TEST_LICENSE))
2124
db.commit()

tests/lib/test_authentication.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import pytest
2+
3+
from fastapi import HTTPException
4+
from sqlalchemy.exc import MultipleResultsFound
5+
from unittest.mock import patch
6+
7+
from mavedb.lib.authentication import get_current_user_data_from_api_key, get_current_user
8+
from mavedb.models.user import User
9+
from mavedb.models.enums.user_role import UserRole
10+
11+
from tests.helpers.constants import TEST_USER, TEST_USER_DECODED_JWT, ADMIN_USER, ADMIN_USER_DECODED_JWT
12+
from tests.helpers.util import create_api_key_for_current_user, mark_user_inactive
13+
14+
15+
@pytest.mark.asyncio
16+
async def test_get_current_user_data_from_key_valid_token(session, setup_lib_db, client):
17+
access_key = create_api_key_for_current_user(client)
18+
user_data = await get_current_user_data_from_api_key(session, access_key)
19+
assert user_data.user.username == TEST_USER["username"]
20+
21+
# Some lingering db transaction holds this test open unless it is explicitly closed.
22+
session.commit()
23+
24+
25+
@pytest.mark.asyncio
26+
async def test_get_current_user_data_from_key_invalid_token(session, setup_lib_db, client):
27+
access_key = create_api_key_for_current_user(client)
28+
user_data = await get_current_user_data_from_api_key(session, f"invalid_{access_key}")
29+
assert user_data is None
30+
31+
# Some lingering db transaction holds this test open unless it is explicitly closed.
32+
session.commit()
33+
34+
35+
@pytest.mark.asyncio
36+
async def test_get_current_user_data_from_key_nonetype_token(session, setup_lib_db, client):
37+
access_key = create_api_key_for_current_user(client)
38+
user_data = await get_current_user_data_from_api_key(session, None)
39+
assert user_data is None
40+
41+
# Some lingering db transaction holds this test open unless it is explicitly closed.
42+
session.commit()
43+
44+
45+
@pytest.mark.asyncio
46+
async def test_get_current_user_via_api_key(session, setup_lib_db, client):
47+
access_key = create_api_key_for_current_user(client)
48+
user_data = await get_current_user_data_from_api_key(session, access_key)
49+
50+
user_data = await get_current_user(user_data, None, session, None)
51+
assert user_data.user.username == TEST_USER["username"]
52+
53+
# Some lingering db transaction holds this test open unless it is explicitly closed.
54+
session.commit()
55+
56+
57+
@pytest.mark.asyncio
58+
async def test_get_current_user_via_token_payload(session, setup_lib_db):
59+
user_data = await get_current_user(None, TEST_USER_DECODED_JWT, session, None)
60+
assert user_data.user.username == TEST_USER["username"]
61+
62+
# Some lingering db transaction holds this test open unless it is explicitly closed.
63+
session.commit()
64+
65+
66+
@pytest.mark.asyncio
67+
async def test_get_current_user_no_api_no_jwt(session, setup_lib_db):
68+
user_data = await get_current_user(None, None, session, None)
69+
assert user_data is None
70+
71+
# Some lingering db transaction holds this test open unless it is explicitly closed.
72+
session.commit()
73+
74+
75+
@pytest.mark.asyncio
76+
async def test_get_current_user_no_username(session, setup_lib_db):
77+
# Remove the username key from the JWT
78+
jwt_without_sub = TEST_USER_DECODED_JWT.copy()
79+
jwt_without_sub.pop("sub")
80+
81+
user_data = await get_current_user(None, jwt_without_sub, session, None)
82+
assert user_data is None
83+
84+
# Some lingering db transaction holds this test open unless it is explicitly closed.
85+
session.commit()
86+
87+
88+
@pytest.mark.asyncio
89+
@pytest.mark.parametrize("with_email", [True, False])
90+
async def test_get_current_user_nonexistent_user(session, setup_lib_db, with_email):
91+
new_user_jwt = {
92+
"sub": "5555-5555-5555-5555",
93+
"given_name": "Temporary",
94+
"family_name": "User",
95+
}
96+
97+
email = "tempemail@test.com" if with_email else None
98+
with patch("mavedb.lib.authentication.fetch_orcid_user_email") as orc_email:
99+
orc_email.return_value = email
100+
user_data = await get_current_user(None, new_user_jwt, session, None)
101+
orc_email.assert_called_once()
102+
103+
assert user_data.user.username == new_user_jwt["sub"]
104+
assert user_data.user.first_name == new_user_jwt["given_name"]
105+
assert user_data.user.last_name == new_user_jwt["family_name"]
106+
assert user_data.user.email == email
107+
108+
# Ensure one user record is in the database
109+
session.query(User).filter(User.username == new_user_jwt["sub"]).one()
110+
111+
# Some lingering db transaction holds this test open unless it is explicitly closed.
112+
session.commit()
113+
114+
115+
@pytest.mark.asyncio
116+
async def test_get_current_user_user_exists_twice(session, setup_lib_db):
117+
session.add(User(**TEST_USER))
118+
session.commit()
119+
120+
with pytest.raises(MultipleResultsFound) as exc_info:
121+
await get_current_user(None, TEST_USER_DECODED_JWT, session, None)
122+
assert "Multiple rows were found when one or none was required" in str(exc_info.value)
123+
124+
# Some lingering db transaction holds this test open unless it is explicitly closed.
125+
session.commit()
126+
127+
128+
@pytest.mark.asyncio
129+
async def test_get_current_user_user_is_inactive(session, setup_lib_db):
130+
mark_user_inactive(session, TEST_USER["username"])
131+
user_data = await get_current_user(None, TEST_USER_DECODED_JWT, session, None)
132+
133+
assert user_data is None
134+
135+
# Some lingering db transaction holds this test open unless it is explicitly closed.
136+
session.commit()
137+
138+
139+
@pytest.mark.asyncio
140+
async def test_get_current_user_set_active_roles(session, setup_lib_db):
141+
user_data = await get_current_user(None, ADMIN_USER_DECODED_JWT, session, "admin")
142+
143+
assert user_data.user.username == ADMIN_USER["username"]
144+
assert UserRole.admin in user_data.active_roles
145+
146+
# Some lingering db transaction holds this test open unless it is explicitly closed.
147+
session.commit()
148+
149+
150+
@pytest.mark.asyncio
151+
async def test_get_current_user_user_with_invalid_role_membership(session, setup_lib_db):
152+
with pytest.raises(HTTPException) as exc_info:
153+
await get_current_user(None, TEST_USER_DECODED_JWT, session, "admin")
154+
assert "This user is not a member of the requested acting role." in str(exc_info.value.detail)
155+
156+
# Some lingering db transaction holds this test open unless it is explicitly closed.
157+
session.commit()
158+
159+
160+
@pytest.mark.asyncio
161+
async def test_get_current_user_user_extraneous_roles(session, setup_lib_db):
162+
user_data = await get_current_user(None, TEST_USER_DECODED_JWT, session, "extra_role")
163+
164+
assert user_data.user.username == TEST_USER["username"]
165+
assert user_data.active_roles == []
166+
167+
# Some lingering db transaction holds this test open unless it is explicitly closed.
168+
session.commit()

0 commit comments

Comments
 (0)