edx-enterprise-data 8.6.1__py3-none-any.whl → 8.8.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {edx_enterprise_data-8.6.1.dist-info → edx_enterprise_data-8.8.0.dist-info}/METADATA +1 -1
  2. {edx_enterprise_data-8.6.1.dist-info → edx_enterprise_data-8.8.0.dist-info}/RECORD +23 -17
  3. enterprise_data/__init__.py +1 -1
  4. enterprise_data/admin_analytics/completions_utils.py +261 -0
  5. enterprise_data/admin_analytics/constants.py +9 -3
  6. enterprise_data/admin_analytics/utils.py +50 -11
  7. enterprise_data/api/v1/paginators.py +1 -1
  8. enterprise_data/api/v1/serializers.py +44 -30
  9. enterprise_data/api/v1/urls.py +21 -4
  10. enterprise_data/api/v1/views/analytics_enrollments.py +42 -53
  11. enterprise_data/api/v1/views/analytics_leaderboard.py +120 -0
  12. enterprise_data/api/v1/views/enterprise_admin.py +9 -5
  13. enterprise_data/api/v1/views/enterprise_completions.py +177 -0
  14. enterprise_data/renderers.py +14 -0
  15. enterprise_data/tests/admin_analytics/mock_analytics_data.py +501 -0
  16. enterprise_data/tests/admin_analytics/mock_enrollments.py +23 -7
  17. enterprise_data/tests/admin_analytics/test_analytics_enrollments.py +23 -22
  18. enterprise_data/tests/admin_analytics/test_analytics_leaderboard.py +163 -0
  19. enterprise_data/tests/admin_analytics/test_enterprise_completions.py +202 -0
  20. enterprise_data/tests/api/v1/views/test_enterprise_admin.py +4 -0
  21. {edx_enterprise_data-8.6.1.dist-info → edx_enterprise_data-8.8.0.dist-info}/LICENSE +0 -0
  22. {edx_enterprise_data-8.6.1.dist-info → edx_enterprise_data-8.8.0.dist-info}/WHEEL +0 -0
  23. {edx_enterprise_data-8.6.1.dist-info → edx_enterprise_data-8.8.0.dist-info}/top_level.txt +0 -0
@@ -8,10 +8,10 @@ from rest_framework import status
8
8
  from rest_framework.reverse import reverse
9
9
  from rest_framework.test import APITransactionTestCase
10
10
 
11
- from enterprise_data.admin_analytics.constants import ENROLLMENT_CSV
12
- from enterprise_data.api.v1.serializers import AdvanceAnalyticsEnrollmentSerializer as EnrollmentSerializer
11
+ from enterprise_data.admin_analytics.constants import EnrollmentChart, ResponseType
13
12
  from enterprise_data.api.v1.serializers import AdvanceAnalyticsEnrollmentStatsSerializer as EnrollmentStatsSerializer
14
- from enterprise_data.tests.admin_analytics.mock_enrollments import (
13
+ from enterprise_data.api.v1.serializers import AdvanceAnalyticsQueryParamSerializer
14
+ from enterprise_data.tests.admin_analytics.mock_analytics_data import (
15
15
  ENROLLMENT_STATS_CSVS,
16
16
  ENROLLMENTS,
17
17
  enrollments_csv_content,
@@ -23,13 +23,13 @@ from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
23
23
  from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
24
24
 
25
25
  INVALID_CALCULATION_ERROR = (
26
- f"Calculation must be one of {EnrollmentSerializer.CALCULATION_CHOICES}"
26
+ f"Calculation must be one of {AdvanceAnalyticsQueryParamSerializer.CALCULATION_CHOICES}"
27
27
  )
28
28
  INVALID_GRANULARITY_ERROR = (
29
- f"Granularity must be one of {EnrollmentSerializer.GRANULARITY_CHOICES}"
29
+ f"Granularity must be one of {AdvanceAnalyticsQueryParamSerializer.GRANULARITY_CHOICES}"
30
30
  )
31
- INVALID_CSV_ERROR1 = f"csv_type must be one of {EnrollmentSerializer.CSV_TYPES}"
32
- INVALID_CSV_ERROR2 = f"csv_type must be one of {EnrollmentStatsSerializer.CSV_TYPES}"
31
+ INVALID_RESPONSE_TYPE_ERROR = f"response_type must be one of {AdvanceAnalyticsQueryParamSerializer.RESPONSE_TYPES}"
32
+ INVALID_CHART_TYPE_ERROR = f"chart_type must be one of {EnrollmentStatsSerializer.CHART_TYPES}"
33
33
 
34
34
 
35
35
  @ddt.ddt
@@ -59,7 +59,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
59
59
  )
60
60
 
61
61
  fetch_max_enrollment_datetime_patcher = patch(
62
- 'enterprise_data.api.v1.views.analytics_enrollments.fetch_max_enrollment_datetime',
62
+ 'enterprise_data.api.v1.views.analytics_enrollments.fetch_enrollments_cache_expiry_timestamp',
63
63
  return_value=datetime.now()
64
64
  )
65
65
 
@@ -100,6 +100,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
100
100
 
101
101
  response = self.client.get(self.url, {"page_size": 2})
102
102
  assert response.status_code == status.HTTP_200_OK
103
+ assert response["Content-Type"] == "application/json"
103
104
  data = response.json()
104
105
  assert data["next"] == f"http://testserver{self.url}?page=2&page_size=2"
105
106
  assert data["previous"] is None
@@ -110,6 +111,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
110
111
 
111
112
  response = self.client.get(self.url, {"page_size": 2, "page": 2})
112
113
  assert response.status_code == status.HTTP_200_OK
114
+ assert response["Content-Type"] == "application/json"
113
115
  data = response.json()
114
116
  assert data["next"] == f"http://testserver{self.url}?page=3&page_size=2"
115
117
  assert data["previous"] == f"http://testserver{self.url}?page_size=2"
@@ -120,6 +122,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
120
122
 
121
123
  response = self.client.get(self.url, {"page_size": 2, "page": 3})
122
124
  assert response.status_code == status.HTTP_200_OK
125
+ assert response["Content-Type"] == "application/json"
123
126
  data = response.json()
124
127
  assert data["next"] is None
125
128
  assert data["previous"] == f"http://testserver{self.url}?page=2&page_size=2"
@@ -130,6 +133,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
130
133
 
131
134
  response = self.client.get(self.url, {"page_size": 5})
132
135
  assert response.status_code == status.HTTP_200_OK
136
+ assert response["Content-Type"] == "application/json"
133
137
  data = response.json()
134
138
  assert data["next"] is None
135
139
  assert data["previous"] is None
@@ -146,15 +150,11 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
146
150
  Test the GET method for the AdvanceAnalyticsIndividualEnrollmentsView return correct CSV data.
147
151
  """
148
152
  mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
149
- response = self.client.get(self.url, {"csv_type": ENROLLMENT_CSV.INDIVIDUAL_ENROLLMENTS.value})
153
+ response = self.client.get(self.url, {"response_type": ResponseType.CSV.value})
150
154
  assert response.status_code == status.HTTP_200_OK
151
155
 
152
156
  # verify the response headers
153
157
  assert response["Content-Type"] == "text/csv"
154
- assert (
155
- response["Content-Disposition"]
156
- == 'attachment; filename="individual_enrollments.csv"'
157
- )
158
158
 
159
159
  # verify the response content
160
160
  content = b"".join(response.streaming_content)
@@ -193,7 +193,7 @@ class TestIndividualEnrollmentsAPI(JWTTestMixin, APITransactionTestCase):
193
193
  "params": {"granularity": "invalid"},
194
194
  "error": {"granularity": [INVALID_GRANULARITY_ERROR]},
195
195
  },
196
- {"params": {"csv_type": "invalid"}, "error": {"csv_type": [INVALID_CSV_ERROR1]}},
196
+ {"params": {"response_type": "invalid"}, "error": {"response_type": [INVALID_RESPONSE_TYPE_ERROR]}},
197
197
  )
198
198
  @ddt.unpack
199
199
  def test_get_invalid_query_params(self, params, error):
@@ -232,7 +232,7 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
232
232
  )
233
233
 
234
234
  fetch_max_enrollment_datetime_patcher = patch(
235
- 'enterprise_data.api.v1.views.analytics_enrollments.fetch_max_enrollment_datetime',
235
+ 'enterprise_data.api.v1.views.analytics_enrollments.fetch_enrollments_cache_expiry_timestamp',
236
236
  return_value=datetime.now()
237
237
  )
238
238
 
@@ -271,6 +271,7 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
271
271
 
272
272
  response = self.client.get(self.url)
273
273
  assert response.status_code == status.HTTP_200_OK
274
+ assert response["Content-Type"] == "application/json"
274
275
  data = response.json()
275
276
  assert data == {
276
277
  "enrollments_over_time": [
@@ -332,21 +333,21 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
332
333
 
333
334
  @patch("enterprise_data.api.v1.views.analytics_enrollments.fetch_and_cache_enrollments_data")
334
335
  @ddt.data(
335
- ENROLLMENT_CSV.ENROLLMENTS_OVER_TIME.value,
336
- ENROLLMENT_CSV.TOP_COURSES_BY_ENROLLMENTS.value,
337
- ENROLLMENT_CSV.TOP_SUBJECTS_BY_ENROLLMENTS.value,
336
+ EnrollmentChart.ENROLLMENTS_OVER_TIME.value,
337
+ EnrollmentChart.TOP_COURSES_BY_ENROLLMENTS.value,
338
+ EnrollmentChart.TOP_SUBJECTS_BY_ENROLLMENTS.value,
338
339
  )
339
- def test_get_csv(self, csv_type, mock_fetch_and_cache_enrollments_data):
340
+ def test_get_csv(self, chart_type, mock_fetch_and_cache_enrollments_data):
340
341
  """
341
342
  Test that AdvanceAnalyticsEnrollmentStatsView return correct CSV data.
342
343
  """
343
344
  mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
344
345
 
345
- response = self.client.get(self.url, {"csv_type": csv_type})
346
+ response = self.client.get(self.url, {"response_type": ResponseType.CSV.value, "chart_type": chart_type})
346
347
  assert response.status_code == status.HTTP_200_OK
347
348
  assert response["Content-Type"] == "text/csv"
348
349
  # verify the response content
349
- assert response.content == ENROLLMENT_STATS_CSVS[csv_type]
350
+ assert response.content == ENROLLMENT_STATS_CSVS[chart_type]
350
351
 
351
352
  @ddt.data(
352
353
  {
@@ -381,7 +382,7 @@ class TestEnrollmentStatsAPI(JWTTestMixin, APITransactionTestCase):
381
382
  "params": {"granularity": "invalid"},
382
383
  "error": {"granularity": [INVALID_GRANULARITY_ERROR]},
383
384
  },
384
- {"params": {"csv_type": "invalid"}, "error": {"csv_type": [INVALID_CSV_ERROR2]}},
385
+ {"params": {"chart_type": "invalid"}, "error": {"chart_type": [INVALID_CHART_TYPE_ERROR]}},
385
386
  )
386
387
  @ddt.unpack
387
388
  def test_get_invalid_query_params(self, params, error):
@@ -0,0 +1,163 @@
1
+ """Unittests for analytics_enrollments.py"""
2
+
3
+ from datetime import datetime
4
+
5
+ import ddt
6
+ from mock import patch
7
+ from rest_framework import status
8
+ from rest_framework.reverse import reverse
9
+ from rest_framework.test import APITransactionTestCase
10
+
11
+ from enterprise_data.admin_analytics.constants import ResponseType
12
+ from enterprise_data.tests.admin_analytics.mock_analytics_data import (
13
+ ENROLLMENTS,
14
+ LEADERBOARD_RESPONSE,
15
+ engagements_dataframe,
16
+ enrollments_dataframe,
17
+ leaderboard_csv_content,
18
+ )
19
+ from enterprise_data.tests.mixins import JWTTestMixin
20
+ from enterprise_data.tests.test_utils import UserFactory
21
+ from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
22
+ from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
23
+
24
+
25
+ @ddt.ddt
26
+ class TestLeaderboardAPI(JWTTestMixin, APITransactionTestCase):
27
+ """Tests for AdvanceAnalyticsLeaderboardView."""
28
+
29
+ def setUp(self):
30
+ """
31
+ Setup method.
32
+ """
33
+ super().setUp()
34
+ self.user = UserFactory(is_staff=True)
35
+ role, __ = EnterpriseDataFeatureRole.objects.get_or_create(
36
+ name=ENTERPRISE_DATA_ADMIN_ROLE
37
+ )
38
+ self.role_assignment = EnterpriseDataRoleAssignment.objects.create(
39
+ role=role, user=self.user
40
+ )
41
+ self.client.force_authenticate(user=self.user)
42
+
43
+ self.enterprise_uuid = "ee5e6b3a-069a-4947-bb8d-d2dbc323396c"
44
+ self.set_jwt_cookie()
45
+
46
+ self.url = reverse(
47
+ "v1:enterprise-admin-analytics-leaderboard",
48
+ kwargs={"enterprise_uuid": self.enterprise_uuid},
49
+ )
50
+
51
+ fetch_max_enrollment_datetime_patcher = patch(
52
+ 'enterprise_data.api.v1.views.analytics_leaderboard.fetch_enrollments_cache_expiry_timestamp',
53
+ return_value=datetime.now()
54
+ )
55
+
56
+ fetch_max_enrollment_datetime_patcher.start()
57
+ self.addCleanup(fetch_max_enrollment_datetime_patcher.stop)
58
+
59
+ fetch_max_engagement_datetime_patcher = patch(
60
+ 'enterprise_data.api.v1.views.analytics_leaderboard.fetch_engagements_cache_expiry_timestamp',
61
+ return_value=datetime.now()
62
+ )
63
+
64
+ fetch_max_engagement_datetime_patcher.start()
65
+ self.addCleanup(fetch_max_engagement_datetime_patcher.stop)
66
+
67
+ def verify_enrollment_data(self, results, results_count):
68
+ """Verify the received enrollment data."""
69
+ attrs = [
70
+ "email",
71
+ "course_title",
72
+ "course_subject",
73
+ "enroll_type",
74
+ "enterprise_enrollment_date",
75
+ ]
76
+
77
+ assert len(results) == results_count
78
+
79
+ filtered_data = []
80
+ for enrollment in ENROLLMENTS:
81
+ for result in results:
82
+ if enrollment["email"] == result["email"]:
83
+ filtered_data.append({attr: enrollment[attr] for attr in attrs})
84
+ break
85
+
86
+ received_data = sorted(results, key=lambda x: x["email"])
87
+ expected_data = sorted(filtered_data, key=lambda x: x["email"])
88
+ assert received_data == expected_data
89
+
90
+ @patch(
91
+ "enterprise_data.api.v1.views.analytics_leaderboard.fetch_and_cache_enrollments_data"
92
+ )
93
+ @patch(
94
+ "enterprise_data.api.v1.views.analytics_leaderboard.fetch_and_cache_engagements_data"
95
+ )
96
+ def test_get(self, mock_fetch_and_cache_engagements_data, mock_fetch_and_cache_enrollments_data):
97
+ """
98
+ Test the GET method for the AdvanceAnalyticsLeaderboardView works.
99
+ """
100
+ mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
101
+ mock_fetch_and_cache_engagements_data.return_value = engagements_dataframe()
102
+
103
+ response = self.client.get(self.url, {"page_size": 2})
104
+ assert response.status_code == status.HTTP_200_OK
105
+ assert response["Content-Type"] == "application/json"
106
+ data = response.json()
107
+ assert data["next"] == f'http://testserver{self.url}?page=2&page_size=2'
108
+ assert data["previous"] is None
109
+ assert data["current_page"] == 1
110
+ assert data["num_pages"] == 6
111
+ assert data["count"] == 12
112
+ assert data["results"] == [
113
+ {
114
+ "email": "paul77@example.org",
115
+ "daily_sessions": 1,
116
+ "learning_time_seconds": 15753,
117
+ "learning_time_hours": 4.4,
118
+ "average_session_length": 4.4,
119
+ "course_completions": None,
120
+ },
121
+ {
122
+ "email": "seth57@example.org",
123
+ "daily_sessions": 1,
124
+ "learning_time_seconds": 9898,
125
+ "learning_time_hours": 2.7,
126
+ "average_session_length": 2.7,
127
+ "course_completions": None,
128
+ },
129
+ ]
130
+
131
+ # fetch all records
132
+ response = self.client.get(self.url, {"page_size": 20})
133
+ assert response.status_code == status.HTTP_200_OK
134
+ data = response.json()
135
+ assert data["next"] is None
136
+ assert data["previous"] is None
137
+ assert data["current_page"] == 1
138
+ assert data["num_pages"] == 1
139
+ assert data["count"] == 12
140
+ assert data["results"] == LEADERBOARD_RESPONSE
141
+
142
+ @patch(
143
+ "enterprise_data.api.v1.views.analytics_leaderboard.fetch_and_cache_enrollments_data"
144
+ )
145
+ @patch(
146
+ "enterprise_data.api.v1.views.analytics_leaderboard.fetch_and_cache_engagements_data"
147
+ )
148
+ def test_get_csv(self, mock_fetch_and_cache_engagements_data, mock_fetch_and_cache_enrollments_data):
149
+ """
150
+ Test the GET method for the AdvanceAnalyticsIndividualEnrollmentsView return correct CSV data.
151
+ """
152
+ mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
153
+ mock_fetch_and_cache_engagements_data.return_value = engagements_dataframe()
154
+
155
+ response = self.client.get(self.url, {"response_type": ResponseType.CSV.value})
156
+ assert response.status_code == status.HTTP_200_OK
157
+
158
+ # verify the response headers
159
+ assert response["Content-Type"] == "text/csv"
160
+
161
+ # verify the response content
162
+ content = b"".join(response.streaming_content)
163
+ assert content == leaderboard_csv_content()
@@ -0,0 +1,202 @@
1
+ """Unitest for EnterpriseAdminCompletionsStatsView."""
2
+ from datetime import datetime
3
+
4
+ import ddt
5
+ from mock import patch
6
+ from rest_framework import status
7
+ from rest_framework.reverse import reverse
8
+ from rest_framework.test import APITransactionTestCase
9
+
10
+ from enterprise_data.admin_analytics.utils import ChartType
11
+ from enterprise_data.tests.admin_analytics.mock_analytics_data import (
12
+ COMPLETIONS_STATS_CSVS,
13
+ ENROLLMENTS,
14
+ enrollments_dataframe,
15
+ )
16
+ from enterprise_data.tests.mixins import JWTTestMixin
17
+ from enterprise_data.tests.test_utils import UserFactory
18
+ from enterprise_data_roles.constants import ENTERPRISE_DATA_ADMIN_ROLE
19
+ from enterprise_data_roles.models import EnterpriseDataFeatureRole, EnterpriseDataRoleAssignment
20
+
21
+
22
+ @ddt.ddt
23
+ class TestCompletionstStatsAPI(JWTTestMixin, APITransactionTestCase):
24
+ """Tests for EnterrpiseAdminCompletionsStatsView."""
25
+
26
+ def setUp(self):
27
+ """
28
+ Setup method.
29
+ """
30
+ super().setUp()
31
+ self.user = UserFactory(is_staff=True)
32
+ role, __ = EnterpriseDataFeatureRole.objects.get_or_create(
33
+ name=ENTERPRISE_DATA_ADMIN_ROLE
34
+ )
35
+ self.role_assignment = EnterpriseDataRoleAssignment.objects.create(
36
+ role=role, user=self.user
37
+ )
38
+ self.client.force_authenticate(user=self.user)
39
+
40
+ self.enterprise_id = "ee5e6b3a-069a-4947-bb8d-d2dbc323396c"
41
+ self.set_jwt_cookie()
42
+
43
+ self.url = reverse(
44
+ "v1:enterprise-admin-analytics-completions-stats",
45
+ kwargs={"enterprise_id": self.enterprise_id},
46
+ )
47
+
48
+ fetch_max_enrollment_datetime_patcher = patch(
49
+ 'enterprise_data.api.v1.views.enterprise_completions.fetch_max_enrollment_datetime',
50
+ return_value=datetime.now()
51
+ )
52
+
53
+ fetch_max_enrollment_datetime_patcher.start()
54
+ self.addCleanup(fetch_max_enrollment_datetime_patcher.stop)
55
+
56
+ @patch(
57
+ "enterprise_data.api.v1.views.enterprise_completions.fetch_and_cache_enrollments_data"
58
+ )
59
+ def test_get(self, mock_fetch_and_cache_enrollments_data):
60
+ """
61
+ Test the GET method for the EnterrpiseAdminCompletionsStatsView works.
62
+ """
63
+ mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
64
+
65
+ params = {
66
+ "start_date": "2020-01-01",
67
+ "end_date": "2025-08-09",
68
+ "calculation": "Running Total",
69
+ "granularity": "Daily",
70
+ }
71
+ response = self.client.get(self.url, params)
72
+ assert response.status_code == status.HTTP_200_OK
73
+ data = response.json()
74
+ assert data == {
75
+ "completions_over_time": [
76
+ {
77
+ "passed_date": "2021-08-25T00:00:00",
78
+ "enroll_type": "certificate",
79
+ "count": 1,
80
+ },
81
+ {
82
+ "passed_date": "2021-09-01T00:00:00",
83
+ "enroll_type": "certificate",
84
+ "count": 2,
85
+ },
86
+ ],
87
+ "top_courses_by_completions": [
88
+ {
89
+ "course_key": "hEmW+tvk03",
90
+ "course_title": "Re-engineered tangible approach",
91
+ "enroll_type": "certificate",
92
+ "count": 2,
93
+ }
94
+ ],
95
+ "top_subjects_by_completions": [
96
+ {
97
+ "course_subject": "business-management",
98
+ "enroll_type": "certificate",
99
+ "count": 2,
100
+ }
101
+ ],
102
+ }
103
+
104
+ @patch("enterprise_data.api.v1.views.enterprise_completions.fetch_and_cache_enrollments_data")
105
+ @ddt.data(
106
+ ChartType.COMPLETIONS_OVER_TIME.value,
107
+ ChartType.TOP_COURSES_BY_COMPLETIONS.value,
108
+ ChartType.TOP_SUBJECTS_BY_COMPLETIONS.value,
109
+ )
110
+ def test_get_csv(self, chart_type, mock_fetch_and_cache_enrollments_data):
111
+ """
112
+ Test that EnterrpiseAdminCompletionsStatsView return correct CSV data.
113
+ """
114
+ mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
115
+ params = {
116
+ 'start_date': '2020-01-01',
117
+ 'end_date': '2025-08-09',
118
+ 'calculation': 'Running Total',
119
+ 'granularity': 'Daily',
120
+ 'response_type': 'csv',
121
+ 'chart_type': chart_type,
122
+ }
123
+ response = self.client.get(self.url, params)
124
+ assert response.status_code == status.HTTP_200_OK
125
+ assert response["Content-Type"] == "text/csv"
126
+ # verify the response content
127
+ assert response.content == COMPLETIONS_STATS_CSVS[chart_type]
128
+
129
+
130
+ @ddt.ddt
131
+ class TestCompletionstAPI(JWTTestMixin, APITransactionTestCase):
132
+ """Tests for EnterrpiseAdminCompletionsView."""
133
+
134
+ def setUp(self):
135
+ """
136
+ Setup method.
137
+ """
138
+ super().setUp()
139
+ self.user = UserFactory(is_staff=True)
140
+ role, __ = EnterpriseDataFeatureRole.objects.get_or_create(
141
+ name=ENTERPRISE_DATA_ADMIN_ROLE
142
+ )
143
+ self.role_assignment = EnterpriseDataRoleAssignment.objects.create(
144
+ role=role, user=self.user
145
+ )
146
+ self.client.force_authenticate(user=self.user)
147
+
148
+ self.enterprise_id = "ee5e6b3a-069a-4947-bb8d-d2dbc323396c"
149
+ self.set_jwt_cookie()
150
+
151
+ self.url = reverse(
152
+ "v1:enterprise-admin-analytics-completions",
153
+ kwargs={"enterprise_id": self.enterprise_id},
154
+ )
155
+
156
+ fetch_max_enrollment_datetime_patcher = patch(
157
+ 'enterprise_data.api.v1.views.enterprise_completions.fetch_max_enrollment_datetime',
158
+ return_value=datetime.now()
159
+ )
160
+
161
+ fetch_max_enrollment_datetime_patcher.start()
162
+ self.addCleanup(fetch_max_enrollment_datetime_patcher.stop)
163
+
164
+ def verify_enrollment_data(self, results, results_count):
165
+ """Verify the received enrollment data."""
166
+ attrs = [
167
+ "email",
168
+ "course_title",
169
+ "course_subject",
170
+ "passed_date",
171
+ ]
172
+
173
+ assert len(results) == results_count
174
+
175
+ filtered_data = []
176
+ for enrollment in ENROLLMENTS:
177
+ for result in results:
178
+ if enrollment["email"] == result["email"]:
179
+ filtered_data.append({attr: enrollment[attr] for attr in attrs})
180
+ break
181
+ received_data = sorted(results, key=lambda x: x["email"])
182
+ expected_data = sorted(filtered_data, key=lambda x: x["email"])
183
+ assert received_data == expected_data
184
+
185
+ @patch(
186
+ "enterprise_data.api.v1.views.enterprise_completions.fetch_and_cache_enrollments_data"
187
+ )
188
+ def test_get(self, mock_fetch_and_cache_enrollments_data):
189
+ """
190
+ Test the GET method for the EnterrpiseAdminCompletionsView works.
191
+ """
192
+ mock_fetch_and_cache_enrollments_data.return_value = enrollments_dataframe()
193
+
194
+ response = self.client.get(self.url, {"page": 1, "page_size": 1})
195
+ assert response.status_code == status.HTTP_200_OK
196
+ data = response.json()
197
+ assert data["next"] == f"http://testserver{self.url}?page=2&page_size=1"
198
+ assert data["previous"] is None
199
+ assert data["current_page"] == 1
200
+ assert data["num_pages"] == 2
201
+ assert data["count"] == 2
202
+ self.verify_enrollment_data(data["results"], 1)
@@ -108,6 +108,10 @@ class TestEnterpriseAdminAnalyticsSkillsView(JWTTestMixin, APITransactionTestCas
108
108
  return [
109
109
  list(item.values()) for item in get_dummy_skills_data(self.enterprise_id)
110
110
  ]
111
+ elif 'fact_enrollment_admin_dash' in query:
112
+ return [
113
+ list(item.values()) for item in get_dummy_enrollments_data(self.enterprise_id, 15)
114
+ ]
111
115
  else:
112
116
  return [
113
117
  list(item.values()) for item in get_dummy_skills_data(self.enterprise_id)