Repository: getmoto/moto
Pull request #8178 (2024-09-29 15:44:36)

Scheduler: generalized start_date parameter validation (create_schedule)
[#8172](https://github.com/getmoto/moto/pull/8172) covered only a use case I was working with. Still, I got suspicious about a few behavior nuances and delved a bit into it, then discovered that it is not only the current today date that throws that exception but any other date before it. The mentioned PR's code raises an exception for `start_date` with today's date but higher properties such as hours, minutes, etc (even though I was able to test all of these properties in  localhost, in GH actions for Python it fails for others times smaller properties as minutes, microseconds, etc. _probably due to -j flag for parallel jobs_). This new PR is more robust and covers other cases slipping on the previous one.

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/getmoto/moto/pull/8178?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 94.53%. Comparing base [(`caf67cc`)](https://app.codecov.io/gh/getmoto/moto/commit/caf67cce4c284045452ea225bd74f5326a0aa33e?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) to head [(`3ff484b`)](https://app.codecov.io/gh/getmoto/moto/commit/3ff484bb889884491edb4fda3d4ad1146f962ad4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).
> Report is 4 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master    #8178   +/-   ##
=======================================
  Coverage   94.53%   94.53%           
=======================================
  Files        1155     1155           
  Lines       99714    99714           
=======================================
  Hits        94266    94266           
  Misses       5448     5448           
```

| [Flag](https://app.codecov.io/gh/getmoto/moto/pull/8178/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | Coverage Δ | |
|---|---|---|
| [servertests](https://app.codecov.io/gh/getmoto/moto/pull/8178/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `28.78% <0.00%> (ø)` | |
| [unittests](https://app.codecov.io/gh/getmoto/moto/pull/8178/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto) | `94.50% <100.00%> (ø)` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/getmoto/moto/pull/8178?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=getmoto).


Patch:
diff --git a/moto/scheduler/models.py b/moto/scheduler/models.py
index 7a2bcc157..4f5cd0fc1 100644
--- a/moto/scheduler/models.py
+++ b/moto/scheduler/models.py
@@ -65,8 +65,8 @@ class Schedule(BaseModel):
         if self.schedule_expression.count("*") and start_date is not None:
             start_date_as_dt = utcfromtimestamp(cast(int, start_date))
             now = utcnow()
-            if start_date_as_dt.date() == now.date():
-                diff = abs(now - start_date_as_dt)
+            if start_date_as_dt < now:
+                diff = now - start_date_as_dt
                 rule = datetime.timedelta(minutes=5)
                 within_rule = diff <= rule
                 if not within_rule:


Test patch:
diff --git a/tests/test_scheduler/__init__.py b/tests/test_scheduler/__init__.py
index e69de29bb..a7b62c967 100644
--- a/tests/test_scheduler/__init__.py
+++ b/tests/test_scheduler/__init__.py
@@ -0,0 +1,32 @@
+from datetime import datetime, timedelta
+from typing import List, Tuple
+
+from moto.core.utils import utcnow
+
+
+def pytest_parametrize_test_create_get_schedule__with_start_date() -> (
+    List[Tuple[datetime, datetime]]
+):
+    now = utcnow()
+    timedelta_kwargs = {"days": 1, "hours": 1, "weeks": 1}
+    return_ = []
+    for k, v in timedelta_kwargs.items():
+        to_append = now + timedelta(**{k: v})
+        return_.append((to_append, to_append.replace(microsecond=0)))
+    else:
+        to_append = now.replace(year=now.year + 1)
+        return_.append((to_append, to_append.replace(microsecond=0)))
+    return return_
+
+
+def pytest_parametrize_test_create_schedule__exception_with_start_date() -> (
+    List[datetime]
+):
+    now = utcnow()
+    timedelta_kwargs = {"days": 1, "minutes": 6, "hours": 1, "weeks": 1}
+    return_ = []
+    for k, v in timedelta_kwargs.items():
+        return_.append(now - timedelta(**{k: v}))
+    else:
+        return_.append(now.replace(year=now.year - 1))
+    return return_
diff --git a/tests/test_scheduler/test_scheduler.py b/tests/test_scheduler/test_scheduler.py
index 6bb672f26..2ef384c63 100644
--- a/tests/test_scheduler/test_scheduler.py
+++ b/tests/test_scheduler/test_scheduler.py
@@ -1,4 +1,4 @@
-from datetime import datetime, timedelta, timezone
+from datetime import datetime, timezone
 
 import boto3
 import pytest
@@ -6,7 +6,10 @@ from botocore.client import ClientError
 
 from moto import mock_aws
 from moto.core import DEFAULT_ACCOUNT_ID
-from moto.core.utils import utcnow
+from tests.test_scheduler import (
+    pytest_parametrize_test_create_get_schedule__with_start_date,
+    pytest_parametrize_test_create_schedule__exception_with_start_date,
+)
 
 # See our Development Tips on writing tests for hints on how to write good tests:
 # http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
@@ -233,10 +236,11 @@ def test_delete_schedule_for_none_existing_schedule():
 
 
 @mock_aws
-def test_create_get_schedule__with_start_date():
-    # Arrange
-    expected_start_date = start_date = utcnow().replace(microsecond=0)
-
+@pytest.mark.parametrize(
+    "start_date,expected_start_date",
+    pytest_parametrize_test_create_get_schedule__with_start_date(),
+)
+def test_create_get_schedule__with_start_date(start_date, expected_start_date):
     # Act
     client = boto3.client("scheduler", region_name="eu-west-1")
     client.create_schedule(
@@ -262,14 +266,16 @@ def test_create_get_schedule__with_start_date():
 
 
 @mock_aws
-def test_create_schedule__exception_with_start_date():
+@pytest.mark.parametrize(
+    "start_date",
+    pytest_parametrize_test_create_schedule__exception_with_start_date(),
+)
+def test_create_schedule__exception_with_start_date(start_date):
     # Arrange
     expected_error = "ValidationException"
     expected_error_message = (
         "The StartDate you specify cannot be earlier than 5 minutes ago."
     )
-    start_date = utcnow() - timedelta(minutes=6)
-
     # Act
     with pytest.raises(ClientError) as exc:
         client = boto3.client("scheduler", region_name="eu-west-1")