@@ -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
9891081class 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
11831396class 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
26022815class TestReinviteUser :
0 commit comments