1+ """Unit tests for the Users resource."""
2+
3+ import copy
4+ from unittest .mock import Mock
5+
6+ import pytest
7+ from pytfe .models .user import User , UserPermissions , UserUpdateCurrentOptions
8+ from pytfe .resources .user import Users
9+
10+
11+ class TestUsers :
12+ """Test suite for user resource operations."""
13+
14+ @pytest .fixture
15+ def mock_transport (self ):
16+ """Mock HTTP transport."""
17+ return Mock ()
18+
19+ @pytest .fixture
20+ def users_service (self , mock_transport ):
21+ """Create users service with mocked transport."""
22+ return Users (mock_transport )
23+
24+ @pytest .fixture
25+ def sample_user_response (self ):
26+ """Sample JSON:API response for a user."""
27+ return {
28+ "data" : {
29+ "id" : "user-MA4GL63FmYRpSFxa" ,
30+ "type" : "users" ,
31+ "attributes" : {
32+ "username" : "admin" ,
33+ "email" : "admin@example.com" ,
34+ "is-service-account" : False ,
35+ "auth-method" : "hcp_sso" ,
36+ "avatar-url" : "https://example.com/avatar.png" ,
37+ "v2-only" : True ,
38+ "permissions" : {
39+ "can-create-organizations" : False ,
40+ "can-change-email" : True ,
41+ "can-change-username" : True ,
42+ },
43+ },
44+ }
45+ }
46+
47+ def test_read_user (self , users_service , mock_transport , sample_user_response ):
48+ """Test reading a specific user by ID."""
49+ mock_transport .request .return_value .json .return_value = sample_user_response
50+
51+ user_id = "user-MA4GL63FmYRpSFxa"
52+ user = users_service .read (user_id )
53+
54+ mock_transport .request .assert_called_once_with (
55+ "GET" , f"/api/v2/users/{ user_id } "
56+ )
57+ assert isinstance (user , User )
58+ assert user .id == user_id
59+ assert user .username == "admin"
60+ assert user .email == "admin@example.com"
61+ assert user .is_service_account is False
62+ assert user .auth_method == "hcp_sso"
63+ assert user .avatar_url == "https://example.com/avatar.png"
64+ assert user .v2_only is True
65+ assert isinstance (user .permissions , UserPermissions )
66+ assert user .permissions is not None
67+ assert user .permissions .can_create_organizations is False
68+ assert user .permissions .can_change_email is True
69+ assert user .permissions .can_change_username is True
70+ assert user .permissions .can_manage_user_tokens is False
71+ assert user .permissions .can_view_2fa_settings is False
72+ assert user .permissions .can_manage_hcp_account is False
73+
74+ def test_read_user_invalid_id (self , users_service ):
75+ """Test reading a user with an invalid user ID."""
76+ with pytest .raises (ValueError , match = "invalid user id" ):
77+ users_service .read ("" )
78+
79+ def test_read_user_with_null_unconfirmed_email (
80+ self , users_service , mock_transport , sample_user_response
81+ ):
82+ """Test reading a user when unconfirmed-email is null."""
83+ sample_user_response ["data" ]["attributes" ]["unconfirmed-email" ] = None
84+ mock_transport .request .return_value .json .return_value = sample_user_response
85+
86+ user = users_service .read ("user-MA4GL63FmYRpSFxa" )
87+
88+ assert isinstance (user , User )
89+ assert user .unconfirmed_email is None
90+
91+ def test_read_user_two_factor_parsing (
92+ self , users_service , mock_transport , sample_user_response
93+ ):
94+ """Test reading a user with two-factor data."""
95+ modified_response = copy .deepcopy (sample_user_response )
96+ modified_response ["data" ]["attributes" ]["two-factor" ] = {
97+ "enabled" : True ,
98+ "verified" : False ,
99+ }
100+ mock_transport .request .return_value .json .return_value = modified_response
101+
102+ user_id = "user-MA4GL63FmYRpSFxa"
103+ user = users_service .read (user_id )
104+
105+ assert user .two_factor is not None
106+ assert user .two_factor .enabled is True
107+ assert user .two_factor .verified is False
108+
109+ def test_read_user_nullable_bools (
110+ self , users_service , mock_transport , sample_user_response
111+ ):
112+ """Test reading a user when pointer-style boolean fields are null."""
113+ modified_response = copy .deepcopy (sample_user_response )
114+ modified_response ["data" ]["attributes" ]["is-site-admin" ] = None
115+ modified_response ["data" ]["attributes" ]["is-admin" ] = None
116+ modified_response ["data" ]["attributes" ]["is-sso-login" ] = None
117+ mock_transport .request .return_value .json .return_value = modified_response
118+
119+ user_id = "user-MA4GL63FmYRpSFxa"
120+ user = users_service .read (user_id )
121+
122+ assert user .is_site_admin is None
123+ assert user .is_admin is None
124+ assert user .is_sso_login is None
125+
126+ def test_read_current_user (
127+ self , users_service , mock_transport , sample_user_response
128+ ):
129+ """Test reading the currently authenticated user."""
130+ mock_transport .request .return_value .json .return_value = sample_user_response
131+
132+ user = users_service .read_current ()
133+
134+ mock_transport .request .assert_called_once_with ("GET" , "/api/v2/account/details" )
135+ assert isinstance (user , User )
136+ assert user .id == "user-MA4GL63FmYRpSFxa"
137+ assert user .username == "admin"
138+ assert user .email == "admin@example.com"
139+
140+ def test_update_current_user (
141+ self , users_service , mock_transport , sample_user_response
142+ ):
143+ """Test updating the currently authenticated user."""
144+ mock_transport .request .return_value .json .return_value = sample_user_response
145+ options = UserUpdateCurrentOptions (
146+ username = "new-admin" ,
147+ email = "new-admin@example.com" ,
148+ )
149+
150+ user = users_service .update_current (options )
151+
152+ mock_transport .request .assert_called_once_with (
153+ "PATCH" ,
154+ "/api/v2/account/update" ,
155+ json_body = {
156+ "data" : {
157+ "type" : "users" ,
158+ "attributes" : {
159+ "username" : "new-admin" ,
160+ "email" : "new-admin@example.com" ,
161+ },
162+ }
163+ },
164+ )
165+ assert isinstance (user , User )
166+ assert user .id == "user-MA4GL63FmYRpSFxa"
0 commit comments