Skip to content

Commit 3486f48

Browse files
committed
test: add unit tests for extended profile form functions
1 parent 1fe427d commit 3486f48

1 file changed

Lines changed: 333 additions & 0 deletions

File tree

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
"""
2+
Unit tests for forms in the accounts API
3+
"""
4+
5+
from unittest.mock import Mock, patch
6+
7+
from django.core.exceptions import ObjectDoesNotExist
8+
from django.test import TestCase
9+
10+
from common.djangoapps.student.tests.factories import UserFactory
11+
from openedx.core.djangoapps.user_api.accounts.forms import (
12+
extract_extended_profile_fields_data,
13+
get_extended_profile_form,
14+
validate_and_get_extended_profile_form,
15+
)
16+
17+
18+
class TestExtractExtendedProfileFieldsData(TestCase):
19+
"""
20+
Tests for extract_extended_profile_fields_data function
21+
"""
22+
23+
def test_extract_valid_extended_profile_data(self):
24+
"""
25+
Test extraction of valid extended profile data
26+
"""
27+
extended_profile = [
28+
{"field_name": "department", "field_value": "Engineering"},
29+
{"field_name": "title", "field_value": "Software Engineer"},
30+
]
31+
32+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
33+
34+
self.assertEqual(errors, {})
35+
self.assertEqual(extracted_data, {"department": "Engineering", "title": "Software Engineer"})
36+
37+
def test_extract_extended_profile_with_none_value(self):
38+
"""
39+
Test that None values are skipped
40+
"""
41+
extended_profile = [
42+
{"field_name": "department", "field_value": "Engineering"},
43+
{"field_name": "title", "field_value": None},
44+
]
45+
46+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
47+
48+
self.assertEqual(errors, {})
49+
self.assertEqual(extracted_data, {"department": "Engineering"})
50+
51+
def test_extract_extended_profile_with_empty_string(self):
52+
"""
53+
Test that empty strings are included
54+
"""
55+
extended_profile = [
56+
{"field_name": "department", "field_value": ""},
57+
{"field_name": "title", "field_value": "Engineer"},
58+
]
59+
60+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
61+
62+
self.assertEqual(errors, {})
63+
self.assertEqual(extracted_data, {"department": "", "title": "Engineer"})
64+
65+
def test_extract_extended_profile_not_a_list(self):
66+
"""
67+
Test error when extended_profile is not a list
68+
"""
69+
extended_profile = "not a list"
70+
71+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
72+
73+
self.assertEqual(extracted_data, {})
74+
self.assertIn("extended_profile", errors)
75+
self.assertEqual(errors["extended_profile"]["developer_message"], "extended_profile must be a list")
76+
77+
def test_extract_extended_profile_with_invalid_field_data(self):
78+
"""
79+
Test that invalid field data entries are skipped (logged but not errored)
80+
"""
81+
extended_profile = [
82+
{"field_name": "department", "field_value": "Engineering"},
83+
"invalid entry", # Not a dict
84+
{"field_name": "title", "field_value": "Engineer"},
85+
]
86+
87+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
88+
89+
# Invalid entry should be skipped, but valid ones should be extracted
90+
self.assertEqual(errors, {})
91+
self.assertEqual(extracted_data, {"department": "Engineering", "title": "Engineer"})
92+
93+
def test_extract_extended_profile_missing_field_name(self):
94+
"""
95+
Test that entries without field_name are skipped
96+
"""
97+
extended_profile = [
98+
{"field_name": "department", "field_value": "Engineering"},
99+
{"field_value": "Engineer"}, # Missing field_name
100+
]
101+
102+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
103+
104+
self.assertEqual(errors, {})
105+
self.assertEqual(extracted_data, {"department": "Engineering"})
106+
107+
def test_extract_extended_profile_empty_list(self):
108+
"""
109+
Test that an empty list returns empty data
110+
"""
111+
extended_profile = []
112+
113+
extracted_data, errors = extract_extended_profile_fields_data(extended_profile)
114+
115+
self.assertEqual(errors, {})
116+
self.assertEqual(extracted_data, {})
117+
118+
119+
class TestGetExtendedProfileForm(TestCase):
120+
"""
121+
Tests for get_extended_profile_form function
122+
"""
123+
124+
def setUp(self):
125+
super().setUp()
126+
self.user = UserFactory.create()
127+
128+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model")
129+
def test_get_extended_profile_form_no_model_configured(self, mock_get_model: Mock):
130+
"""
131+
Test when no extended profile model is configured
132+
"""
133+
mock_get_model.side_effect = ImportError("No model configured")
134+
extended_profile_fields_data = {"department": "Engineering"}
135+
136+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
137+
138+
self.assertIsNone(form)
139+
self.assertEqual(errors, {})
140+
141+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model")
142+
def test_get_extended_profile_form_model_has_no_objects(self, mock_get_model: Mock):
143+
"""
144+
Test when model doesn't have objects attribute (AttributeError)
145+
"""
146+
mock_get_model.return_value = None
147+
extended_profile_fields_data = {"department": "Engineering"}
148+
149+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
150+
151+
self.assertIsNone(form)
152+
self.assertEqual(errors, {})
153+
154+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_registration_extension_form")
155+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model")
156+
def test_get_extended_profile_form_with_existing_instance(self, mock_get_model: Mock, mock_get_form: Mock):
157+
"""
158+
Test form creation with an existing profile instance
159+
"""
160+
mock_model = Mock()
161+
mock_instance = Mock()
162+
mock_model.objects.get.return_value = mock_instance
163+
mock_get_model.return_value = mock_model
164+
mock_form_instance = Mock()
165+
mock_form_instance.is_valid.return_value = True
166+
mock_get_form.return_value = mock_form_instance
167+
extended_profile_fields_data = {"department": "Engineering"}
168+
169+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
170+
171+
self.assertEqual(form, mock_form_instance)
172+
self.assertEqual(errors, {})
173+
mock_model.objects.get.assert_called_once_with(user=self.user)
174+
mock_get_form.assert_called_once_with(data=extended_profile_fields_data, instance=mock_instance)
175+
176+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_registration_extension_form")
177+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model")
178+
def test_get_extended_profile_form_without_existing_instance(self, mock_get_model: Mock, mock_get_form: Mock):
179+
"""
180+
Test form creation for a new profile (no existing instance)
181+
"""
182+
mock_model = Mock()
183+
mock_model.DoesNotExist = ObjectDoesNotExist
184+
mock_model.objects.get.side_effect = ObjectDoesNotExist("Profile not found")
185+
mock_get_model.return_value = mock_model
186+
mock_form_instance = Mock()
187+
mock_form_instance.is_valid.return_value = True
188+
mock_get_form.return_value = mock_form_instance
189+
extended_profile_fields_data = {"department": "Engineering"}
190+
191+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
192+
193+
self.assertEqual(form, mock_form_instance)
194+
self.assertEqual(errors, {})
195+
mock_model.objects.get.assert_called_once_with(user=self.user)
196+
mock_get_form.assert_called_once_with(data=extended_profile_fields_data)
197+
198+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_registration_extension_form")
199+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model")
200+
def test_get_extended_profile_form_validation_errors(self, _: Mock, mock_get_form: Mock):
201+
"""
202+
Test when form validation fails
203+
"""
204+
mock_form_instance = Mock()
205+
mock_form_instance.is_valid.return_value = False
206+
mock_form_instance.errors = {"department": ["This field is required"], "title": ["Invalid value"]}
207+
mock_get_form.return_value = mock_form_instance
208+
extended_profile_fields_data = {}
209+
210+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
211+
212+
self.assertEqual(form, mock_form_instance)
213+
self.assertIn("department", errors)
214+
self.assertIn("title", errors)
215+
self.assertEqual(errors["department"]["user_message"], "This field is required")
216+
self.assertEqual(errors["title"]["user_message"], "Invalid value")
217+
218+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_registration_extension_form")
219+
def test_get_extended_profile_form_returns_none(self, mock_get_form: Mock):
220+
"""
221+
Test when get_registration_extension_form returns None
222+
"""
223+
mock_get_form.return_value = None
224+
extended_profile_fields_data = {"department": "Engineering"}
225+
226+
with patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model"):
227+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
228+
229+
self.assertIsNone(form)
230+
self.assertEqual(errors, {})
231+
232+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_registration_extension_form")
233+
def test_get_extended_profile_form_exception_during_creation(self, mock_get_form: Mock):
234+
"""
235+
Test when an unexpected exception occurs during form creation
236+
"""
237+
mock_get_form.side_effect = Exception("Unexpected error")
238+
extended_profile_fields_data = {"department": "Engineering"}
239+
240+
with patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_model"):
241+
form, errors = get_extended_profile_form(extended_profile_fields_data, self.user)
242+
243+
self.assertIsNone(form)
244+
self.assertIn("extended_profile", errors)
245+
self.assertIn("Error creating custom form", errors["extended_profile"]["developer_message"])
246+
247+
248+
class TestValidateAndGetExtendedProfileForm(TestCase):
249+
"""
250+
Tests for validate_and_get_extended_profile_form function
251+
"""
252+
253+
def setUp(self):
254+
super().setUp()
255+
self.user = UserFactory.create()
256+
257+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_form")
258+
@patch("openedx.core.djangoapps.user_api.accounts.forms.extract_extended_profile_fields_data")
259+
def test_validate_with_valid_data(self, mock_extract: Mock, mock_get_form: Mock):
260+
"""
261+
Test successful validation with valid data
262+
"""
263+
extended_profile_data = [{"field_name": "department", "field_value": "Engineering"}]
264+
mock_extract.return_value = ({"department": "Engineering"}, {})
265+
mock_form = Mock()
266+
mock_get_form.return_value = (mock_form, {})
267+
268+
form, errors = validate_and_get_extended_profile_form(extended_profile_data, self.user)
269+
270+
self.assertEqual(form, mock_form)
271+
self.assertEqual(errors, {})
272+
mock_extract.assert_called_once_with(extended_profile_data)
273+
mock_get_form.assert_called_once_with({"department": "Engineering"}, self.user)
274+
275+
@patch("openedx.core.djangoapps.user_api.accounts.forms.extract_extended_profile_fields_data")
276+
def test_validate_with_extraction_errors(self, mock_extract: Mock):
277+
"""
278+
Test when extraction fails
279+
"""
280+
extended_profile_data = "invalid data"
281+
mock_extract.return_value = ({}, {"extended_profile": {"developer_message": "Invalid format"}})
282+
283+
form, errors = validate_and_get_extended_profile_form(extended_profile_data, self.user)
284+
285+
self.assertIsNone(form)
286+
self.assertIn("extended_profile", errors)
287+
288+
@patch("openedx.core.djangoapps.user_api.accounts.forms.extract_extended_profile_fields_data")
289+
def test_validate_with_empty_data(self, mock_extract: Mock):
290+
"""
291+
Test when extracted data is empty
292+
"""
293+
extended_profile_data = []
294+
mock_extract.return_value = ({}, {})
295+
296+
form, errors = validate_and_get_extended_profile_form(extended_profile_data, self.user)
297+
298+
self.assertIsNone(form)
299+
self.assertEqual(errors, {})
300+
301+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_form")
302+
@patch("openedx.core.djangoapps.user_api.accounts.forms.extract_extended_profile_fields_data")
303+
def test_validate_with_form_errors(self, mock_extract: Mock, mock_get_form: Mock):
304+
"""
305+
Test when form validation fails
306+
"""
307+
extended_profile_data = [{"field_name": "department", "field_value": ""}]
308+
mock_extract.return_value = ({"department": ""}, {})
309+
mock_form = Mock()
310+
form_errors = {"department": {"developer_message": "Required field"}}
311+
mock_get_form.return_value = (mock_form, form_errors)
312+
313+
form, errors = validate_and_get_extended_profile_form(extended_profile_data, self.user)
314+
315+
self.assertEqual(form, mock_form)
316+
self.assertIn("department", errors)
317+
318+
@patch("openedx.core.djangoapps.user_api.accounts.forms.get_extended_profile_form")
319+
@patch("openedx.core.djangoapps.user_api.accounts.forms.extract_extended_profile_fields_data")
320+
def test_validate_merges_errors(self, mock_extract: Mock, mock_get_form: Mock):
321+
"""
322+
Test that extraction and form errors are merged
323+
"""
324+
extended_profile_data = [{"field_name": "department", "field_value": "Engineering"}]
325+
mock_extract.return_value = ({"department": "Engineering"}, {})
326+
mock_form = Mock()
327+
form_errors = {"title": {"developer_message": "Required field"}}
328+
mock_get_form.return_value = (mock_form, form_errors)
329+
330+
form, errors = validate_and_get_extended_profile_form(extended_profile_data, self.user)
331+
332+
self.assertEqual(form, mock_form)
333+
self.assertIn("title", errors)

0 commit comments

Comments
 (0)