Skip to content

Commit aed8847

Browse files
committed
fix: improve test coverage
1 parent c58e231 commit aed8847

File tree

4 files changed

+228
-11
lines changed

4 files changed

+228
-11
lines changed

clients/admin-ui/src/features/user-management/EditUserForm.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ const ReinviteSection = ({ user }: ReinviteSectionProps) => {
9393
message={user.invite_expired ? "Invite expired" : "Invite pending"}
9494
description={
9595
user.invite_expired
96-
? "This user's invitation has expired. Click the button below to send a new invitation email."
96+
? "This user's invitation has expired. Use the button to send a new invitation email."
9797
: "This user has not yet accepted their invitation. You can resend the invitation if needed."
9898
}
9999
type={user.invite_expired ? "warning" : "info"}

clients/admin-ui/src/pages/login.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from "~/features/auth";
3333
import { CustomTextInput } from "~/features/common/form/inputs";
3434
import { passwordValidation } from "~/features/common/form/validation";
35+
import { getErrorMessage } from "~/features/common/helpers";
3536
import { useGetAllOpenIDProvidersSimpleQuery } from "~/features/openid-authentication/openprovider.slice";
3637

3738
const parseQueryParam = (query: ParsedUrlQuery) => {
@@ -138,10 +139,13 @@ const useLogin = () => {
138139
} catch (error) {
139140
// eslint-disable-next-line no-console
140141
console.log(error);
142+
const errorMsg = getErrorMessage(
143+
error,
144+
"Login failed. Please check your credentials and try again.",
145+
);
141146
toast({
142147
status: "error",
143-
description:
144-
"Login failed. Please check your credentials and try again.",
148+
description: errorMsg,
145149
});
146150
}
147151
};

src/fides/api/api/v1/endpoints/user_endpoints.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -840,12 +840,6 @@ def verify_invite_code(
840840
user_invite = FidesUserInvite.get_by(db, field="username", value=username)
841841

842842
if not user_invite:
843-
raise HTTPException(
844-
status_code=HTTP_404_NOT_FOUND,
845-
detail="User not found.",
846-
)
847-
848-
if not user_invite.invite_code_valid(invite_code):
849843
raise HTTPException(
850844
status_code=HTTP_400_BAD_REQUEST,
851845
detail="Invite code is invalid.",
@@ -857,6 +851,12 @@ def verify_invite_code(
857851
detail="Invite code has expired.",
858852
)
859853

854+
if not user_invite.invite_code_valid(invite_code):
855+
raise HTTPException(
856+
status_code=HTTP_400_BAD_REQUEST,
857+
detail="Invite code is invalid.",
858+
)
859+
860860
return user_invite
861861

862862

tests/ops/api/v1/endpoints/test_user_endpoints.py

Lines changed: 215 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,98 @@ def test_get_users_include_external_with_username_filter(
985985
for user in regular_users:
986986
assert user.id in user_ids
987987

988+
def test_get_users_includes_invite_status(
989+
self,
990+
api_client: TestClient,
991+
generate_auth_header,
992+
url: str,
993+
db,
994+
):
995+
"""Test that get_users includes invite status fields for all users"""
996+
from datetime import timedelta, timezone
997+
998+
password = str_to_b64_str("Password123!")
999+
1000+
# User with active invite
1001+
user_with_invite = FidesUser.create(
1002+
db=db,
1003+
data={
1004+
"username": "user_with_invite",
1005+
"email_address": "[email protected]",
1006+
"disabled": True,
1007+
},
1008+
)
1009+
FidesUserInvite.create(
1010+
db=db,
1011+
data={"username": "user_with_invite", "invite_code": "test_code"},
1012+
)
1013+
1014+
# User with expired invite
1015+
user_with_expired = FidesUser.create(
1016+
db=db,
1017+
data={
1018+
"username": "user_expired",
1019+
"email_address": "[email protected]",
1020+
"disabled": True,
1021+
},
1022+
)
1023+
expired_invite = FidesUserInvite.create(
1024+
db=db,
1025+
data={"username": "user_expired", "invite_code": "test_code"},
1026+
)
1027+
expired_invite.updated_at = datetime.now(timezone.utc) - timedelta(
1028+
hours=INVITE_CODE_TTL_HOURS + 1
1029+
)
1030+
expired_invite.save(db)
1031+
1032+
# User without invite
1033+
user_no_invite = FidesUser.create(
1034+
db=db,
1035+
data={
1036+
"username": "user_no_invite",
1037+
"password": password,
1038+
"email_address": "[email protected]",
1039+
},
1040+
)
1041+
1042+
# User without username
1043+
user_no_username = FidesUser.create(
1044+
db=db,
1045+
data={
1046+
"username": None,
1047+
"email_address": "[email protected]",
1048+
},
1049+
)
1050+
1051+
auth_header = generate_auth_header(scopes=[USER_READ])
1052+
resp = api_client.get(url, headers=auth_header)
1053+
assert resp.status_code == HTTP_200_OK
1054+
response_body = resp.json()
1055+
1056+
# Find users in response
1057+
users_by_id = {user["id"]: user for user in response_body["items"]}
1058+
1059+
# Check user with active invite
1060+
assert users_by_id[user_with_invite.id]["has_invite"] is True
1061+
assert users_by_id[user_with_invite.id]["invite_expired"] is False
1062+
1063+
# Check user with expired invite
1064+
assert users_by_id[user_with_expired.id]["has_invite"] is True
1065+
assert users_by_id[user_with_expired.id]["invite_expired"] is True
1066+
1067+
# Check user without invite
1068+
assert users_by_id[user_no_invite.id]["has_invite"] is False
1069+
assert users_by_id[user_no_invite.id]["invite_expired"] is None
1070+
1071+
# Check user without username
1072+
assert users_by_id[user_no_username.id]["has_invite"] is False
1073+
assert users_by_id[user_no_username.id]["invite_expired"] is None
1074+
1075+
user_with_invite.delete(db)
1076+
user_with_expired.delete(db)
1077+
user_no_invite.delete(db)
1078+
user_no_username.delete(db)
1079+
9881080

9891081
class TestGetUser:
9901082
@pytest.fixture(scope="function")
@@ -1054,6 +1146,9 @@ def test_get_user_with_user_read_own_scope_own_data(
10541146
assert user_data["username"] == respondent.username
10551147
assert user_data["id"] == respondent.id
10561148
assert user_data["created_at"] == stringify_date(respondent.created_at)
1149+
# Verify invite status fields are included
1150+
assert "has_invite" in user_data
1151+
assert "invite_expired" in user_data
10571152

10581153
def test_get_user_with_user_read_own_scope_other_user_data(
10591154
self,
@@ -1179,6 +1274,124 @@ def test_get_user_with_root_user_can_access_any_user(
11791274
assert user_data["id"] == test_user.id
11801275
assert user_data["created_at"] == stringify_date(test_user.created_at)
11811276

1277+
def test_get_user_includes_invite_status_with_active_invite(
1278+
self,
1279+
api_client: TestClient,
1280+
generate_auth_header,
1281+
url_no_id: str,
1282+
db,
1283+
):
1284+
"""Test that get_user includes invite status fields when user has an active invite"""
1285+
user = FidesUser.create(
1286+
db=db,
1287+
data={
1288+
"username": "invited_user",
1289+
"email_address": "[email protected]",
1290+
"disabled": True,
1291+
},
1292+
)
1293+
FidesUserInvite.create(
1294+
db=db,
1295+
data={"username": "invited_user", "invite_code": "test_code"},
1296+
)
1297+
1298+
auth_header = generate_auth_header(scopes=[USER_READ])
1299+
resp = api_client.get(f"{url_no_id}/{user.id}", headers=auth_header)
1300+
assert resp.status_code == HTTP_200_OK
1301+
user_data = resp.json()
1302+
assert user_data["has_invite"] is True
1303+
assert user_data["invite_expired"] is False
1304+
1305+
user.delete(db)
1306+
1307+
def test_get_user_includes_invite_status_with_expired_invite(
1308+
self,
1309+
api_client: TestClient,
1310+
generate_auth_header,
1311+
url_no_id: str,
1312+
db,
1313+
):
1314+
"""Test that get_user includes invite status fields when user has an expired invite"""
1315+
from datetime import timedelta, timezone
1316+
1317+
user = FidesUser.create(
1318+
db=db,
1319+
data={
1320+
"username": "invited_user",
1321+
"email_address": "[email protected]",
1322+
"disabled": True,
1323+
},
1324+
)
1325+
invite = FidesUserInvite.create(
1326+
db=db,
1327+
data={"username": "invited_user", "invite_code": "test_code"},
1328+
)
1329+
invite.updated_at = datetime.now(timezone.utc) - timedelta(
1330+
hours=INVITE_CODE_TTL_HOURS + 1
1331+
)
1332+
invite.save(db)
1333+
1334+
auth_header = generate_auth_header(scopes=[USER_READ])
1335+
resp = api_client.get(f"{url_no_id}/{user.id}", headers=auth_header)
1336+
assert resp.status_code == HTTP_200_OK
1337+
user_data = resp.json()
1338+
assert user_data["has_invite"] is True
1339+
assert user_data["invite_expired"] is True
1340+
1341+
user.delete(db)
1342+
1343+
def test_get_user_includes_invite_status_without_invite(
1344+
self,
1345+
api_client: TestClient,
1346+
generate_auth_header,
1347+
url_no_id: str,
1348+
db,
1349+
):
1350+
"""Test that get_user includes invite status fields when user has no invite"""
1351+
password = str_to_b64_str("Password123!")
1352+
user = FidesUser.create(
1353+
db=db,
1354+
data={
1355+
"username": "no_invite_user",
1356+
"password": password,
1357+
"email_address": "[email protected]",
1358+
},
1359+
)
1360+
1361+
auth_header = generate_auth_header(scopes=[USER_READ])
1362+
resp = api_client.get(f"{url_no_id}/{user.id}", headers=auth_header)
1363+
assert resp.status_code == HTTP_200_OK
1364+
user_data = resp.json()
1365+
assert user_data["has_invite"] is False
1366+
assert user_data["invite_expired"] is None
1367+
1368+
user.delete(db)
1369+
1370+
def test_get_user_includes_invite_status_without_username(
1371+
self,
1372+
api_client: TestClient,
1373+
generate_auth_header,
1374+
url_no_id: str,
1375+
db,
1376+
):
1377+
"""Test that get_user includes invite status fields when user has no username"""
1378+
user = FidesUser.create(
1379+
db=db,
1380+
data={
1381+
"username": None,
1382+
"email_address": "[email protected]",
1383+
},
1384+
)
1385+
1386+
auth_header = generate_auth_header(scopes=[USER_READ])
1387+
resp = api_client.get(f"{url_no_id}/{user.id}", headers=auth_header)
1388+
assert resp.status_code == HTTP_200_OK
1389+
user_data = resp.json()
1390+
assert user_data["has_invite"] is False
1391+
assert user_data["invite_expired"] is None
1392+
1393+
user.delete(db)
1394+
11821395

11831396
class TestUpdateUser:
11841397
@pytest.fixture(scope="function")
@@ -2595,8 +2808,8 @@ def test_accept_invite_nonexistent_user(self, api_client, url):
25952808
"new_password": "Testpassword1!",
25962809
},
25972810
)
2598-
assert response.status_code == HTTP_404_NOT_FOUND
2599-
assert response.json()["detail"] == "User not found."
2811+
assert response.status_code == HTTP_400_BAD_REQUEST
2812+
assert response.json()["detail"] == "Invite code is invalid."
26002813

26012814

26022815
class TestReinviteUser:

0 commit comments

Comments
 (0)