|
24 | 24 | from ops_api.ops.services.agreements import ( |
25 | 25 | AgreementsService, |
26 | 26 | _compute_agreement_totals, |
| 27 | + _compute_days_in_procurement_step, |
27 | 28 | _compute_procurement_overview, |
28 | 29 | _compute_procurement_step_summary, |
29 | 30 | ) |
@@ -1810,3 +1811,152 @@ def test_zero_division_guard(self): |
1810 | 1811 | result = _compute_procurement_step_summary([], fiscal_year=2025) |
1811 | 1812 | for step in result["step_data"]: |
1812 | 1813 | assert step["agreements_percent"] == 0.0 |
| 1814 | + |
| 1815 | + |
| 1816 | +def _make_mock_step(step_number, step_start_date=None, step_completed_date=None): |
| 1817 | + """Helper to create a mock procurement tracker step.""" |
| 1818 | + step = MagicMock() |
| 1819 | + step.step_number = step_number |
| 1820 | + step.step_start_date = step_start_date |
| 1821 | + step.step_completed_date = step_completed_date |
| 1822 | + return step |
| 1823 | + |
| 1824 | + |
| 1825 | +def _make_mock_tracker_with_steps(active_step_number, steps, status=None): |
| 1826 | + """Helper to create a mock procurement tracker with steps.""" |
| 1827 | + from models.procurement_tracker import ProcurementTrackerStatus |
| 1828 | + |
| 1829 | + tracker = MagicMock() |
| 1830 | + tracker.active_step_number = active_step_number |
| 1831 | + tracker.status = status if status is not None else ProcurementTrackerStatus.ACTIVE |
| 1832 | + tracker.steps = steps |
| 1833 | + return tracker |
| 1834 | + |
| 1835 | + |
| 1836 | +class TestComputeDaysInProcurementStep: |
| 1837 | + def test_empty_list(self): |
| 1838 | + result = _compute_days_in_procurement_step([]) |
| 1839 | + assert result == {} |
| 1840 | + |
| 1841 | + def test_agreement_without_tracker(self): |
| 1842 | + ag = _make_mock_procurement_agreement(trackers=[]) |
| 1843 | + result = _compute_days_in_procurement_step([ag]) |
| 1844 | + assert result == {} |
| 1845 | + |
| 1846 | + def test_completed_step_uses_completed_date(self): |
| 1847 | + step = _make_mock_step(3, step_start_date=date(2025, 1, 1), step_completed_date=date(2025, 1, 11)) |
| 1848 | + tracker = _make_mock_tracker_with_steps(3, steps=[step]) |
| 1849 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=10) |
| 1850 | + |
| 1851 | + result = _compute_days_in_procurement_step([ag]) |
| 1852 | + |
| 1853 | + assert result == {3: {10: 10}} |
| 1854 | + |
| 1855 | + @patch("ops_api.ops.services.agreements.date") |
| 1856 | + def test_incomplete_step_uses_today(self, mock_date): |
| 1857 | + mock_date.today.return_value = date(2025, 3, 1) |
| 1858 | + mock_date.side_effect = lambda *args, **kwargs: date(*args, **kwargs) |
| 1859 | + |
| 1860 | + step = _make_mock_step(2, step_start_date=date(2025, 1, 1), step_completed_date=None) |
| 1861 | + tracker = _make_mock_tracker_with_steps(2, steps=[step]) |
| 1862 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=5) |
| 1863 | + |
| 1864 | + result = _compute_days_in_procurement_step([ag]) |
| 1865 | + |
| 1866 | + assert result == {2: {5: 59}} |
| 1867 | + |
| 1868 | + def test_skips_inactive_tracker(self): |
| 1869 | + from models.procurement_tracker import ProcurementTrackerStatus |
| 1870 | + |
| 1871 | + step = _make_mock_step(1, step_start_date=date(2025, 1, 1)) |
| 1872 | + tracker = _make_mock_tracker_with_steps(1, steps=[step], status=ProcurementTrackerStatus.INACTIVE) |
| 1873 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1874 | + |
| 1875 | + result = _compute_days_in_procurement_step([ag]) |
| 1876 | + assert result == {} |
| 1877 | + |
| 1878 | + def test_skips_completed_tracker(self): |
| 1879 | + from models.procurement_tracker import ProcurementTrackerStatus |
| 1880 | + |
| 1881 | + step = _make_mock_step(1, step_start_date=date(2025, 1, 1)) |
| 1882 | + tracker = _make_mock_tracker_with_steps(1, steps=[step], status=ProcurementTrackerStatus.COMPLETED) |
| 1883 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1884 | + |
| 1885 | + result = _compute_days_in_procurement_step([ag]) |
| 1886 | + assert result == {} |
| 1887 | + |
| 1888 | + def test_skips_step_out_of_range(self): |
| 1889 | + step = _make_mock_step(7, step_start_date=date(2025, 1, 1)) |
| 1890 | + tracker = _make_mock_tracker_with_steps(7, steps=[step]) |
| 1891 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1892 | + |
| 1893 | + result = _compute_days_in_procurement_step([ag]) |
| 1894 | + assert result == {} |
| 1895 | + |
| 1896 | + def test_skips_step_zero(self): |
| 1897 | + step = _make_mock_step(0, step_start_date=date(2025, 1, 1)) |
| 1898 | + tracker = _make_mock_tracker_with_steps(0, steps=[step]) |
| 1899 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1900 | + |
| 1901 | + result = _compute_days_in_procurement_step([ag]) |
| 1902 | + assert result == {} |
| 1903 | + |
| 1904 | + def test_skips_step_without_start_date(self): |
| 1905 | + step = _make_mock_step(3, step_start_date=None) |
| 1906 | + tracker = _make_mock_tracker_with_steps(3, steps=[step]) |
| 1907 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1908 | + |
| 1909 | + result = _compute_days_in_procurement_step([ag]) |
| 1910 | + assert result == {} |
| 1911 | + |
| 1912 | + def test_skips_when_active_step_not_in_steps_list(self): |
| 1913 | + step = _make_mock_step(1, step_start_date=date(2025, 1, 1)) |
| 1914 | + tracker = _make_mock_tracker_with_steps(4, steps=[step]) # active_step_number=4 but only step 1 exists |
| 1915 | + ag = _make_mock_procurement_agreement(trackers=[tracker], agreement_id=1) |
| 1916 | + |
| 1917 | + result = _compute_days_in_procurement_step([ag]) |
| 1918 | + assert result == {} |
| 1919 | + |
| 1920 | + def test_multiple_agreements_same_step(self): |
| 1921 | + step1 = _make_mock_step(2, step_start_date=date(2025, 1, 1), step_completed_date=date(2025, 1, 6)) |
| 1922 | + tracker1 = _make_mock_tracker_with_steps(2, steps=[step1]) |
| 1923 | + ag1 = _make_mock_procurement_agreement(trackers=[tracker1], agreement_id=10) |
| 1924 | + |
| 1925 | + step2 = _make_mock_step(2, step_start_date=date(2025, 1, 1), step_completed_date=date(2025, 1, 21)) |
| 1926 | + tracker2 = _make_mock_tracker_with_steps(2, steps=[step2]) |
| 1927 | + ag2 = _make_mock_procurement_agreement(trackers=[tracker2], agreement_id=20) |
| 1928 | + |
| 1929 | + result = _compute_days_in_procurement_step([ag1, ag2]) |
| 1930 | + |
| 1931 | + assert result == {2: {10: 5, 20: 20}} |
| 1932 | + |
| 1933 | + def test_multiple_agreements_different_steps(self): |
| 1934 | + step1 = _make_mock_step(1, step_start_date=date(2025, 1, 1), step_completed_date=date(2025, 1, 4)) |
| 1935 | + tracker1 = _make_mock_tracker_with_steps(1, steps=[step1]) |
| 1936 | + ag1 = _make_mock_procurement_agreement(trackers=[tracker1], agreement_id=1) |
| 1937 | + |
| 1938 | + step2 = _make_mock_step(5, step_start_date=date(2025, 2, 1), step_completed_date=date(2025, 2, 15)) |
| 1939 | + tracker2 = _make_mock_tracker_with_steps(5, steps=[step2]) |
| 1940 | + ag2 = _make_mock_procurement_agreement(trackers=[tracker2], agreement_id=2) |
| 1941 | + |
| 1942 | + result = _compute_days_in_procurement_step([ag1, ag2]) |
| 1943 | + |
| 1944 | + assert result == {1: {1: 3}, 5: {2: 14}} |
| 1945 | + |
| 1946 | + def test_uses_active_tracker_ignores_completed(self): |
| 1947 | + from models.procurement_tracker import ProcurementTrackerStatus |
| 1948 | + |
| 1949 | + step_completed = _make_mock_step(2, step_start_date=date(2025, 1, 1), step_completed_date=date(2025, 1, 5)) |
| 1950 | + completed_tracker = _make_mock_tracker_with_steps( |
| 1951 | + 2, steps=[step_completed], status=ProcurementTrackerStatus.COMPLETED |
| 1952 | + ) |
| 1953 | + |
| 1954 | + step_active = _make_mock_step(4, step_start_date=date(2025, 3, 1), step_completed_date=date(2025, 3, 11)) |
| 1955 | + active_tracker = _make_mock_tracker_with_steps(4, steps=[step_active]) |
| 1956 | + |
| 1957 | + ag = _make_mock_procurement_agreement(trackers=[completed_tracker, active_tracker], agreement_id=7) |
| 1958 | + |
| 1959 | + result = _compute_days_in_procurement_step([ag]) |
| 1960 | + |
| 1961 | + assert result == {4: {7: 10}} |
| 1962 | + assert 2 not in result |
0 commit comments