|
2 | 2 | Unit tests for instructor API v2 endpoints. |
3 | 3 | """ |
4 | 4 | import json |
5 | | -from datetime import datetime |
| 5 | +from datetime import datetime, timedelta |
6 | 6 | from unittest.mock import Mock, patch |
7 | 7 | from urllib.parse import urlencode |
8 | 8 | from uuid import uuid4 |
@@ -1848,6 +1848,107 @@ def test_extension_data_structure(self, mock_title_or_url, mock_get_units, mock_ |
1848 | 1848 | self.assertIsInstance(extension['unit_title'], str) # noqa: PT009 |
1849 | 1849 | self.assertIsInstance(extension['unit_location'], str) # noqa: PT009 |
1850 | 1850 |
|
| 1851 | + def test_reset_extension_with_none_date_excluded(self): |
| 1852 | + """ |
| 1853 | + Test that extensions reset via set_date_for_block(None) are excluded from results. |
| 1854 | + When an extension is reset, edx-when creates a UserDate with abs_date=None and rel_date=None, |
| 1855 | + causing actual_date to fall back to the original block due date. These reverted overrides |
| 1856 | + should not appear as granted extensions. |
| 1857 | + """ |
| 1858 | + original_due = datetime.now(UTC).replace(microsecond=0) |
| 1859 | + extended = original_due + timedelta(days=60) |
| 1860 | + set_dates_for_course(self.course_key, [(self.subsection.location, {'due': original_due})]) |
| 1861 | + |
| 1862 | + # Grant extension to student1, then reset it by passing None |
| 1863 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student1) |
| 1864 | + set_date_for_block(self.course_key, self.subsection.location, 'due', None, user=self.student1) |
| 1865 | + |
| 1866 | + # Grant a real extension to student2 |
| 1867 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student2) |
| 1868 | + |
| 1869 | + self.client.force_authenticate(user=self.instructor) |
| 1870 | + response = self.client.get(self._get_url()) |
| 1871 | + |
| 1872 | + assert response.status_code == 200 |
| 1873 | + results = response.data['results'] |
| 1874 | + assert len(results) == 1 |
| 1875 | + assert results[0]['username'] == 'student2' |
| 1876 | + assert results[0]['extended_due_date'] == extended.strftime('%Y-%m-%dT%H:%M:%SZ') |
| 1877 | + |
| 1878 | + def test_reset_extension_matching_original_date_excluded(self): |
| 1879 | + """ |
| 1880 | + Test that extensions whose override date matches the original due date are excluded. |
| 1881 | + When an extension is reset, the override reverts to the original subsection date, |
| 1882 | + making it appear as if there's an active extension when there isn't one. |
| 1883 | + """ |
| 1884 | + original_due = datetime.now(UTC).replace(microsecond=0) |
| 1885 | + extended = original_due + timedelta(days=60) |
| 1886 | + set_dates_for_course(self.course_key, [(self.subsection.location, {'due': original_due})]) |
| 1887 | + |
| 1888 | + # Grant extension to student1, then "reset" it by setting it back to the original date |
| 1889 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student1) |
| 1890 | + set_date_for_block(self.course_key, self.subsection.location, 'due', original_due, user=self.student1) |
| 1891 | + |
| 1892 | + # Grant a real extension to student2 |
| 1893 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student2) |
| 1894 | + |
| 1895 | + self.client.force_authenticate(user=self.instructor) |
| 1896 | + response = self.client.get(self._get_url()) |
| 1897 | + |
| 1898 | + assert response.status_code == 200 |
| 1899 | + results = response.data['results'] |
| 1900 | + assert len(results) == 1 |
| 1901 | + assert results[0]['username'] == 'student2' |
| 1902 | + assert results[0]['extended_due_date'] == extended.strftime('%Y-%m-%dT%H:%M:%SZ') |
| 1903 | + |
| 1904 | + def test_reset_extension_excluded_with_block_id_filter(self): |
| 1905 | + """ |
| 1906 | + Test that reset extensions are also excluded when filtering by block_id. |
| 1907 | + """ |
| 1908 | + original_due = datetime.now(UTC).replace(microsecond=0) |
| 1909 | + extended = original_due + timedelta(days=60) |
| 1910 | + set_dates_for_course(self.course_key, [(self.subsection.location, {'due': original_due})]) |
| 1911 | + |
| 1912 | + # Grant extension to student1, then reset it |
| 1913 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student1) |
| 1914 | + set_date_for_block(self.course_key, self.subsection.location, 'due', None, user=self.student1) |
| 1915 | + |
| 1916 | + # Grant a real extension to student2 |
| 1917 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended, user=self.student2) |
| 1918 | + |
| 1919 | + self.client.force_authenticate(user=self.instructor) |
| 1920 | + params = {'block_id': str(self.subsection.location)} |
| 1921 | + response = self.client.get(self._get_url(), params) |
| 1922 | + |
| 1923 | + assert response.status_code == 200 |
| 1924 | + results = response.data['results'] |
| 1925 | + assert len(results) == 1 |
| 1926 | + assert results[0]['username'] == 'student2' |
| 1927 | + assert results[0]['extended_due_date'] == extended.strftime('%Y-%m-%dT%H:%M:%SZ') |
| 1928 | + |
| 1929 | + def test_active_extensions_still_returned(self): |
| 1930 | + """ |
| 1931 | + Test that legitimate extensions (date differs from original) are still returned. |
| 1932 | + """ |
| 1933 | + original_due = datetime.now(UTC).replace(microsecond=0) |
| 1934 | + extended1 = original_due + timedelta(days=30) |
| 1935 | + extended2 = original_due + timedelta(days=60) |
| 1936 | + set_dates_for_course(self.course_key, [(self.subsection.location, {'due': original_due})]) |
| 1937 | + |
| 1938 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended1, user=self.student1) |
| 1939 | + set_date_for_block(self.course_key, self.subsection.location, 'due', extended2, user=self.student2) |
| 1940 | + |
| 1941 | + self.client.force_authenticate(user=self.instructor) |
| 1942 | + response = self.client.get(self._get_url()) |
| 1943 | + |
| 1944 | + assert response.status_code == 200 |
| 1945 | + results = response.data['results'] |
| 1946 | + assert len(results) == 2 |
| 1947 | + results_by_username = {r['username']: r for r in results} |
| 1948 | + assert results_by_username['student1']['extended_due_date'] == extended1.strftime('%Y-%m-%dT%H:%M:%SZ') |
| 1949 | + assert results_by_username['student2']['extended_due_date'] == extended2.strftime('%Y-%m-%dT%H:%M:%SZ') |
| 1950 | + |
| 1951 | + |
1851 | 1952 | @ddt.ddt |
1852 | 1953 | class IssuedCertificatesViewTest(SharedModuleStoreTestCase): |
1853 | 1954 | """ |
|
0 commit comments