Skip to content

Commit d538a2c

Browse files
robbailiff2Karthikeyannhsdependabot[bot]oneeb-nhseddalmond1
authored
Eli 674/support active iterations at specified time (#599)
* ELI-615 | campaign having recent - active start_date supersedes the others sharing same best-status * ELI-615 | more linting * ELI-615 | revert commit * ELI-615 | wip * ELI-615 | wip * ELI-615 | wip * Bump werkzeug from 3.1.5 to 3.1.6 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](pallets/werkzeug@3.1.5...3.1.6) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * Updated not_member_of operator to NotMemberOf (#594) * Added vulture to workflows (#585) * Added vulture to workflows * Added new make commands and added to project * Added updated lockfile * Minimal config with no errors * Corrected vulture commands * Generating new lock file * ELI-615 | modified iterations_result to iteration result * ELI-615 | fix - naming issues | handle stop iter exception * ELI-615 | campaign_configs - fixture updated | test case fixed * ELI-615 | fix flaky tests do to fixture scope * ELI-615 | fix flaky tests - removed best status test * ELI-615 | used raw campagin config for tests using iteration dates * ELI-615 | fix - campaign group is used correctly * ELI-615 | fix test_campaigns_grouped_by_condition_name_filters_correctly * ELI-615 | fix tests * ELI-615 | linting * ELI-615 | renamed best_iteration_result to iteration_result_summary * ELI-615 | add more test cases - it tests * ELI-615 | test commit - try git leaks ignore * updated iteration time * Eli 615 : fix - multi campaign target collision (#593) * ELI-615 | campaign having recent - active start_date supersedes the others sharing same best-status * ELI-615 | more linting * ELI-615 | revert commit * ELI-615 | wip * ELI-615 | wip * ELI-615 | wip * Bump werkzeug from 3.1.5 to 3.1.6 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](pallets/werkzeug@3.1.5...3.1.6) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * Updated not_member_of operator to NotMemberOf (#594) * Added vulture to workflows (#585) * Added vulture to workflows * Added new make commands and added to project * Added updated lockfile * Minimal config with no errors * Corrected vulture commands * Generating new lock file * ELI-615 | modified iterations_result to iteration result * ELI-615 | fix - naming issues | handle stop iter exception * ELI-615 | campaign_configs - fixture updated | test case fixed * ELI-615 | fix flaky tests do to fixture scope * ELI-615 | fix flaky tests - removed best status test * ELI-615 | used raw campagin config for tests using iteration dates * ELI-615 | fix - campaign group is used correctly * ELI-615 | fix test_campaigns_grouped_by_condition_name_filters_correctly * ELI-615 | fix tests * ELI-615 | linting * ELI-615 | renamed best_iteration_result to iteration_result_summary * ELI-615 | add more test cases - it tests * ELI-615 | test commit - try git leaks ignore * ELI-615 | incorporated review comments --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: oneeb-nhs <258801025+oneeb-nhs@users.noreply.github.com> Co-authored-by: Robert Bailiff <rob.bailiff1@nhs.net> * ELI-674 | fix - vs code alignment anomalies * ELI-674 |wip - updated iteration_datetime property * ELI-674 - test_iteration_full_datetime_validation checks for datetime * updated iteration time * updated test cases for iteration_time * ELI-674 - fixed flaky test, which was due to iteration/campaign factory * ELI-674 - tweaks to validator * ELI-674 - tweaks to validator * ELI-674 - tweaks to validator * ELI-674 - tweaks to validator * ELI-674 - tweaks to validator * reverted default_iteration_time to iteration_time * ELI-674 - fix errors * ELI-674 - new test case for validators * ELI-674 - linting * ELI-674 - fixed unit tests * ELI-674 - fixed integration tests * Create *.instructions.md * eja - refining instruction files using Github best practice prompt * eja - fixing secret scan and vale * added ignore for gitleaks as well as an allow for the file * Eli 615 : fix - multi campaign target collision (#593) * ELI-615 | campaign having recent - active start_date supersedes the others sharing same best-status * ELI-615 | more linting * ELI-615 | revert commit * ELI-615 | wip * ELI-615 | wip * ELI-615 | wip * Bump werkzeug from 3.1.5 to 3.1.6 Bumps [werkzeug](https://github.com/pallets/werkzeug) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/werkzeug/releases) - [Changelog](https://github.com/pallets/werkzeug/blob/main/CHANGES.rst) - [Commits](pallets/werkzeug@3.1.5...3.1.6) --- updated-dependencies: - dependency-name: werkzeug dependency-version: 3.1.6 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * Updated not_member_of operator to NotMemberOf (#594) * Added vulture to workflows (#585) * Added vulture to workflows * Added new make commands and added to project * Added updated lockfile * Minimal config with no errors * Corrected vulture commands * Generating new lock file * ELI-615 | modified iterations_result to iteration result * ELI-615 | fix - naming issues | handle stop iter exception * ELI-615 | campaign_configs - fixture updated | test case fixed * ELI-615 | fix flaky tests do to fixture scope * ELI-615 | fix flaky tests - removed best status test * ELI-615 | used raw campagin config for tests using iteration dates * ELI-615 | fix - campaign group is used correctly * ELI-615 | fix test_campaigns_grouped_by_condition_name_filters_correctly * ELI-615 | fix tests * ELI-615 | linting * ELI-615 | renamed best_iteration_result to iteration_result_summary * ELI-615 | add more test cases - it tests * ELI-615 | test commit - try git leaks ignore * ELI-615 | incorporated review comments --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: oneeb-nhs <258801025+oneeb-nhs@users.noreply.github.com> Co-authored-by: Robert Bailiff <rob.bailiff1@nhs.net> * eli-537 enabling WAF blocks * eli-537 deleting dev deployment * eli-537 added US to permitted geos, for preprod only, to allow github action tests to still flow * eli-537 amending rate limit * eli-537 minor changes based on initial review * eli-537 removing Production from description as we also deploy to PreProd * eli-537 adding ignore for gitleaks * Bump authlib from 1.6.6 to 1.6.7 Bumps [authlib](https://github.com/authlib/authlib) from 1.6.6 to 1.6.7. - [Release notes](https://github.com/authlib/authlib/releases) - [Changelog](https://github.com/authlib/authlib/blob/main/docs/changelog.rst) - [Commits](authlib/authlib@v1.6.6...v1.6.7) --- updated-dependencies: - dependency-name: authlib dependency-version: 1.6.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * ELI-615 | campaign having recent - active start_date supersedes the others sharing same best-status * ELI-615 | wip * Added vulture to workflows (#585) * Added vulture to workflows * Added new make commands and added to project * Added updated lockfile * Minimal config with no errors * Corrected vulture commands * Generating new lock file * ELI-615 | campaign_configs - fixture updated | test case fixed * ELI-615 | renamed best_iteration_result to iteration_result_summary * ELI-674 - revert - rebasing * ELI-674 - pytest for rules_validation/app.py * ELI-674 - updated comments for sonar fix * ELI-674 - sonar suppression * Revert "ELI-674 - sonar suppression" This reverts commit b4efff2. * ELI-674 - linting suppression * ELI-674 - incorporate review comments - use @before for set_parent methods --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: karthikeyannhs <174426205+Karthikeyannhs@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: oneeb-nhs <258801025+oneeb-nhs@users.noreply.github.com> Co-authored-by: eddalmond1 <102675624+eddalmond1@users.noreply.github.com>
1 parent 13aca73 commit d538a2c

10 files changed

Lines changed: 717 additions & 58 deletions

File tree

src/eligibility_signposting_api/model/campaign_config.py

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import re
55
import typing
66
from collections import Counter
7-
from datetime import UTC, date, datetime
7+
from datetime import UTC, date, datetime, time
88
from enum import StrEnum
99
from functools import cached_property
1010
from operator import attrgetter
@@ -33,6 +33,7 @@
3333
IterationVersion = NewType("IterationVersion", int)
3434
IterationID = NewType("IterationID", str)
3535
IterationDate = NewType("IterationDate", date)
36+
IterationTime = NewType("IterationTime", time)
3637
RuleName = NewType("RuleName", str)
3738
RuleDescription = NewType("RuleDescription", str)
3839
RulePriority = NewType("RulePriority", int)
@@ -50,6 +51,38 @@
5051
RuleText = NewType("RuleText", str)
5152

5253

54+
class DateUtil:
55+
@staticmethod
56+
def parse_date_yyyymmdd(v: str | date) -> date:
57+
if isinstance(v, date):
58+
return v
59+
v_str = str(v)
60+
if not re.fullmatch(r"\d{8}", v_str):
61+
msg = f"Invalid format: {v_str}. Must be YYYYMMDD."
62+
raise ValueError(msg)
63+
try:
64+
return datetime.strptime(v_str, "%Y%m%d").date() # noqa: DTZ007
65+
except ValueError as err:
66+
msg = f"Invalid date value: {v_str}."
67+
raise ValueError(msg) from err
68+
69+
@staticmethod
70+
def parse_time_hhmmss(v: str | time | None) -> time | None:
71+
if not v:
72+
return None
73+
if isinstance(v, time):
74+
return v
75+
v_str = str(v).strip()
76+
if re.fullmatch(r"^\d{2}:\d{2}:\d{2}$", v_str):
77+
try:
78+
return datetime.strptime(v_str, "%H:%M:%S").time() # noqa: DTZ007
79+
except ValueError as err:
80+
msg = f"Invalid time value: {v_str}."
81+
raise ValueError(msg) from err
82+
msg = f"Invalid format: {v_str}. Must be HH:MM:SS."
83+
raise ValueError(msg)
84+
85+
5386
class RuleType(StrEnum):
5487
filter = "F"
5588
suppression = "S"
@@ -262,6 +295,7 @@ class Iteration(BaseModel):
262295
version: IterationVersion = Field(..., alias="Version")
263296
name: IterationName = Field(..., alias="Name")
264297
iteration_date: IterationDate = Field(..., alias="IterationDate")
298+
iteration_time: IterationTime | None = Field(default=None, alias="IterationTime")
265299
iteration_number: int | None = Field(None, alias="IterationNumber")
266300
approval_minimum: int | None = Field(None, alias="ApprovalMinimum")
267301
approval_maximum: int | None = Field(None, alias="ApprovalMaximum")
@@ -277,35 +311,49 @@ class Iteration(BaseModel):
277311

278312
model_config = {"populate_by_name": True, "arbitrary_types_allowed": True, "extra": "ignore"}
279313

280-
def __init__(self, **data: dict[str, typing.Any]) -> None:
281-
super().__init__(**data)
282-
# Ensure each rule knows its parent iteration
283-
for rule in self.iteration_rules:
284-
rule.set_parent(self)
314+
@model_validator(mode="after")
315+
def _link_parent_to_iteration_rules(self) -> typing.Self:
316+
for iteration in self.iteration_rules:
317+
iteration.set_parent(self)
318+
return self
285319

286320
@field_validator("iteration_date", mode="before")
287321
@classmethod
288322
def parse_dates(cls, v: str | date) -> date:
289-
if isinstance(v, date):
290-
return v
291-
292-
v_str = str(v)
323+
return DateUtil.parse_date_yyyymmdd(v)
293324

294-
if not re.fullmatch(r"\d{8}", v_str):
295-
msg = f"Invalid format: {v_str}. Must be YYYYMMDD with 8 digits."
296-
raise ValueError(msg)
297-
298-
try:
299-
return datetime.strptime(v_str, "%Y%m%d").date() # noqa: DTZ007
300-
except ValueError as err:
301-
msg = f"Invalid date value: {v_str}. Must be a valid calendar date in YYYYMMDD format."
302-
raise ValueError(msg) from err
325+
@field_validator("iteration_time", mode="before")
326+
@classmethod
327+
def parse_times(cls, v: str | time) -> time | None:
328+
return DateUtil.parse_time_hhmmss(v)
303329

304330
@field_serializer("iteration_date", when_used="always")
305331
@staticmethod
306332
def serialize_dates(v: date, _info: SerializationInfo) -> str:
307333
return v.strftime("%Y%m%d")
308334

335+
@field_serializer("iteration_time", when_used="always")
336+
@staticmethod
337+
def serialize_time(v: time, _info: SerializationInfo) -> str | None:
338+
return v.strftime("%H:%M:%S") if v else None
339+
340+
_parent: CampaignConfig | None = PrivateAttr(default=None)
341+
342+
def set_parent(self, parent: CampaignConfig) -> None:
343+
self._parent = parent
344+
345+
@cached_property
346+
def iteration_datetime(self) -> datetime:
347+
if self.iteration_time:
348+
iteration_time = self.iteration_time
349+
elif self._parent:
350+
iteration_time = self._parent.iteration_time
351+
else:
352+
msg = f"No iteration_time and no parent linked for iteration {self.id}"
353+
raise ValueError(msg)
354+
355+
return datetime.combine(self.iteration_date, iteration_time).replace(tzinfo=UTC)
356+
309357
def __str__(self) -> str:
310358
return json.dumps(self.model_dump(by_alias=True), indent=2)
311359

@@ -321,7 +369,7 @@ class CampaignConfig(BaseModel):
321369
reviewer: list[str] | None = Field(None, alias="Reviewer")
322370
iteration_frequency: Literal["X", "D", "W", "M", "Q", "A"] = Field(..., alias="IterationFrequency")
323371
iteration_type: Literal["A", "M", "S", "O"] = Field(..., alias="IterationType")
324-
iteration_time: str | None = Field(None, alias="IterationTime")
372+
iteration_time: IterationTime = Field(default=IterationTime(time(0, 0, 0)), alias="IterationTime")
325373
default_comms_routing: str | None = Field(None, alias="DefaultCommsRouting")
326374
start_date: StartDate = Field(..., alias="StartDate")
327375
end_date: EndDate = Field(..., alias="EndDate")
@@ -331,29 +379,33 @@ class CampaignConfig(BaseModel):
331379

332380
model_config = {"populate_by_name": True, "arbitrary_types_allowed": True, "extra": "ignore"}
333381

382+
@model_validator(mode="after")
383+
def _link_parent_to_iterations(self) -> typing.Self:
384+
for iteration in self.iterations:
385+
iteration.set_parent(self)
386+
387+
return self
388+
334389
@field_validator("start_date", "end_date", mode="before")
335390
@classmethod
336391
def parse_dates(cls, v: str | date) -> date:
337-
if isinstance(v, date):
338-
return v
339-
340-
v_str = str(v)
341-
342-
if not re.fullmatch(r"\d{8}", v_str):
343-
msg = f"Invalid format: {v_str}. Must be YYYYMMDD with 8 digits."
344-
raise ValueError(msg)
392+
return DateUtil.parse_date_yyyymmdd(v)
345393

346-
try:
347-
return datetime.strptime(v_str, "%Y%m%d").date() # noqa: DTZ007
348-
except ValueError as err:
349-
msg = f"Invalid date value: {v_str}. Must be a valid calendar date in YYYYMMDD format."
350-
raise ValueError(msg) from err
394+
@field_validator("iteration_time", mode="before")
395+
@classmethod
396+
def parse_times(cls, v: str | time) -> time | None:
397+
return DateUtil.parse_time_hhmmss(v)
351398

352399
@field_serializer("start_date", "end_date", when_used="always")
353400
@staticmethod
354401
def serialize_dates(v: date, _info: SerializationInfo) -> str:
355402
return v.strftime("%Y%m%d")
356403

404+
@field_serializer("iteration_time", when_used="always")
405+
@staticmethod
406+
def serialize_time(v: time, _info: SerializationInfo) -> str | None:
407+
return v.strftime("%H:%M:%S") if v else None
408+
357409
@model_validator(mode="after")
358410
def check_start_and_end_dates_sensible(self) -> typing.Self:
359411
if self.start_date > self.end_date:

src/eligibility_signposting_api/services/processors/campaign_evaluator.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def get_campaign_with_latest_iteration(self, active_campaigns: list[CampaignConf
3131

3232
for cc in active_campaigns:
3333
try:
34-
valid_items.append((cc.current_iteration.iteration_date, cc))
34+
valid_items.append((cc.current_iteration.iteration_datetime, cc))
3535
except StopIteration:
3636
logger.info(
3737
"Skipping campaign ID %s as no active iteration was found.",
@@ -41,13 +41,15 @@ def get_campaign_with_latest_iteration(self, active_campaigns: list[CampaignConf
4141
if not valid_items:
4242
latest_campaign = None
4343
else:
44-
max_date = max(item[0] for item in valid_items)
45-
cc_with_max_iteration_date: list[CampaignConfig] = [item[1] for item in valid_items if item[0] == max_date]
44+
max_date_time = max(item[0] for item in valid_items)
45+
cc_with_max_iteration_date: list[CampaignConfig] = [
46+
item[1] for item in valid_items if item[0] == max_date_time
47+
]
4648
if len(cc_with_max_iteration_date) > 1:
4749
err_msg = (
4850
f"Ambiguous result: '{len(cc_with_max_iteration_date)}' active iterations "
4951
f"for target {cc_with_max_iteration_date[0].target} "
50-
f"found for date '{max_date}' "
52+
f"found for datetime '{max_date_time}' "
5153
f"across campaign(s) {[cc.id for cc in cc_with_max_iteration_date]}"
5254
)
5355
raise ValueError(err_msg)

src/rules_validation_api/app.py

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import logging
44
import sys
55
from collections import defaultdict
6+
from datetime import UTC, datetime
7+
from operator import attrgetter
68
from pathlib import Path
79

810
from pydantic import ValidationError
@@ -74,18 +76,60 @@ def main() -> None: # pragma: no cover
7476

7577

7678
def display_current_iteration(result: RulesValidation) -> None:
77-
no_of_iterations = 0
78-
try:
79-
no_of_iterations = len(result.campaign_config.iterations)
80-
current = result.campaign_config.current_iteration
81-
except StopIteration:
82-
current = None
83-
if current is None:
84-
sys.stdout.write(f"{YELLOW}No active iteration could be determined{RESET}\n")
85-
sys.stdout.write(f"{YELLOW}Total iterations configured: {RESET}{GREEN}{no_of_iterations}{RESET}\n")
79+
config = result.campaign_config
80+
iterations = config.iterations
81+
is_campaign_live = config.campaign_live
82+
today = datetime.now(tz=UTC).date()
83+
84+
no_of_iterations = len(iterations)
85+
is_campaign_expired = config.end_date < today
86+
87+
# ---- Current Iteration ----
88+
if is_campaign_live:
89+
sys.stdout.write(f"{YELLOW}Campaign is {RESET}{GREEN}LIVE{RESET}\n")
90+
try:
91+
current = config.current_iteration
92+
if current:
93+
sys.stdout.write(
94+
f"{YELLOW}Current active Iteration Number: {RESET}{GREEN}{current.iteration_number}{RESET}\n"
95+
)
96+
tz = current.iteration_datetime.tzinfo
97+
sys.stdout.write(
98+
f"{YELLOW}Current active Iteration's date&time: "
99+
f"{RESET}{GREEN}{current.iteration_datetime} ({tz}){RESET}\n"
100+
)
101+
except StopIteration:
102+
sys.stdout.write(f"{YELLOW}No active iteration could be determined{RESET}\n")
103+
86104
else:
87-
sys.stdout.write(f"{YELLOW}Current Iteration Number: {RESET}{GREEN}{current.iteration_number}{RESET}\n")
88-
sys.stdout.write(f"{YELLOW}Total iterations configured: {RESET}{GREEN}{no_of_iterations}{RESET}\n")
105+
sys.stdout.write(f"{YELLOW}Campaign is {RESET}{GREEN}NOT LIVE{RESET} ")
106+
107+
if is_campaign_expired:
108+
sys.stdout.write(f"{YELLOW}[EXPIRED on {config.end_date}]{RESET}\n")
109+
else:
110+
sys.stdout.write(f"{YELLOW}[To be STARTED on {RESET}{GREEN}{config.start_date}{RESET}{YELLOW}]{RESET}\n")
111+
112+
# ---- Next Iteration ----
113+
if not is_campaign_expired:
114+
sorted_iterations = sorted(iterations, key=attrgetter("iteration_date"))
115+
116+
try:
117+
next_iteration = next((i for i in sorted_iterations if i.iteration_date > today), None)
118+
119+
if next_iteration:
120+
sys.stdout.write(
121+
f"{YELLOW}Next active Iteration Number: {RESET}{GREEN}{next_iteration.iteration_number}{RESET}\n"
122+
)
123+
tz = next_iteration.iteration_datetime.tzinfo
124+
sys.stdout.write(
125+
f"{YELLOW}Next active Iteration's date&time: "
126+
f"{RESET}{GREEN}{next_iteration.iteration_datetime} ({tz}){RESET}\n"
127+
)
128+
except StopIteration:
129+
sys.stdout.write(f"{YELLOW}No next active iteration could be determined{RESET}\n")
130+
131+
# ---- Total Iterations ----
132+
sys.stdout.write(f"{YELLOW}Total iterations configured: {RESET}{GREEN}{no_of_iterations}{RESET}\n")
89133

90134

91135
if __name__ == "__main__": # pragma: no cover

src/rules_validation_api/validators/campaign_config_validator.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ def validate_iterations_have_unique_id(self) -> typing.Self:
3434
raise ValueError(msg)
3535
return self
3636

37+
@model_validator(mode="after")
38+
def validate_iterations_have_unique_number(self) -> typing.Self:
39+
numbers = [iteration.iteration_number for iteration in self.iterations]
40+
duplicates = {i_id for i_id, count in Counter(numbers).items() if count > 1}
41+
if duplicates:
42+
msg = f"Iterations contain duplicate numbers: {', '.join(str(i) for i in duplicates)}"
43+
raise ValueError(msg)
44+
return self
45+
3746
@model_validator(mode="after")
3847
def validate_campaign_has_iteration_within_schedule(self) -> typing.Self:
3948
errors: list[str] = []

tests/fixtures/builders/model/rule.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import UTC, date, datetime, timedelta
1+
from datetime import UTC, date, datetime, time, timedelta
22
from operator import attrgetter
33
from random import randint
44

@@ -16,6 +16,7 @@
1616
Iteration,
1717
IterationCohort,
1818
IterationRule,
19+
IterationTime,
1920
RuleAttributeLevel,
2021
RuleAttributeName,
2122
RuleComparator,
@@ -89,13 +90,15 @@ class IterationFactory(ModelFactory[Iteration]):
8990
default_comms_routing = "defaultcomms"
9091
actions_mapper = Use(ActionsMapperFactory.build)
9192
rules_mapper = None
93+
iteration_time = None
9294

9395

9496
class RawCampaignConfigFactory(ModelFactory[CampaignConfig]):
9597
iterations = Use(IterationFactory.batch, size=2)
9698
id = "42-hi5tch-hi5kers-gu5ide-t2o-t3he-gal6axy"
9799
start_date = Use(past_date)
98100
end_date = Use(future_date)
101+
iteration_time = IterationTime(time(0, 0, 0))
99102

100103

101104
class CampaignConfigFactory(RawCampaignConfigFactory):

tests/integration/in_process/test_eligibility_endpoint.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@
3333
from tests.integration.conftest import UNIQUE_CONSUMER_HEADER
3434

3535

36-
def today():
36+
def today() -> date:
3737
return datetime.now(UTC).date()
3838

3939

40-
def yesterday():
40+
def yesterday() -> date:
4141
return datetime.now(UTC).date() - timedelta(days=1)
4242

4343

44-
def tomorrow():
44+
def tomorrow() -> date:
4545
return datetime.now(UTC).date() + timedelta(days=1)
4646

4747

@@ -1532,11 +1532,10 @@ def test_if_multiple_active_iterations_with_same_iteration_datetime_for_the_same
15321532
)
15331533
),
15341534
)
1535-
15361535
err_msg = (
15371536
"Ambiguous result: '2' active iterations "
15381537
"for target RSV "
1539-
f"found for date '{previous_day}' "
1538+
f"found for datetime '{previous_day} 00:00:00+00:00' "
15401539
"across campaign(s) ['RSV_campaign_id_1', 'RSV_campaign_id_2']"
15411540
)
15421541
assert any(err_msg in message for message in caplog.messages), (

0 commit comments

Comments
 (0)