Skip to content

Commit 5c2fcc3

Browse files
authored
Merge branch 'main' into bug/ELI-404
2 parents b78505d + bb4aee5 commit 5c2fcc3

5 files changed

Lines changed: 103 additions & 31 deletions

File tree

src/eligibility_signposting_api/model/campaign_config.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -228,21 +228,6 @@ def check_no_overlapping_iterations(self) -> typing.Self:
228228
raise ValueError(message)
229229
return self
230230

231-
@model_validator(mode="after")
232-
def check_has_iteration_from_start(self) -> typing.Self:
233-
iterations_by_date = sorted(self.iterations, key=attrgetter("iteration_date"))
234-
if first_iteration := next(iter(iterations_by_date), None):
235-
if first_iteration.iteration_date > self.start_date:
236-
message = (
237-
f"campaign {self.id} starts on {self.start_date}, "
238-
f"1st iteration starts later - {first_iteration.iteration_date}"
239-
)
240-
raise ValueError(message)
241-
return self
242-
# Should never happen, since we are constraining self.iterations with a min_length of 1
243-
message = f"campaign {self.id} has no iterations."
244-
raise ValueError(message)
245-
246231
@cached_property
247232
def campaign_live(self) -> bool:
248233
today = datetime.now(tz=UTC).date()

src/rules_validation_api/validators/campaign_config_validator.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
from pydantic import field_validator
1+
import typing
2+
from operator import attrgetter
3+
4+
from pydantic import field_validator, model_validator
25

36
from eligibility_signposting_api.model.campaign_config import CampaignConfig, Iteration
47
from rules_validation_api.validators.iteration_validator import IterationValidation
@@ -9,3 +12,18 @@ class CampaignConfigValidation(CampaignConfig):
912
@field_validator("iterations")
1013
def validate_iterations(cls, iterations: list[Iteration]) -> list[IterationValidation]:
1114
return [IterationValidation(**i.model_dump()) for i in iterations]
15+
16+
@model_validator(mode="after")
17+
def check_has_iteration_from_start(self) -> typing.Self:
18+
iterations_by_date = sorted(self.iterations, key=attrgetter("iteration_date"))
19+
if first_iteration := next(iter(iterations_by_date), None):
20+
if first_iteration.iteration_date > self.start_date:
21+
message = (
22+
f"campaign {self.id} starts on {self.start_date}, "
23+
f"1st iteration starts later - {first_iteration.iteration_date}"
24+
)
25+
raise ValueError(message)
26+
return self
27+
# Should never happen, since we are constraining self.iterations with a min_length of 1
28+
message = f"campaign {self.id} has no iterations."
29+
raise ValueError(message)

tests/integration/conftest.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import datetime
12
import json
23
import logging
34
import os
@@ -19,7 +20,9 @@
1920
from eligibility_signposting_api.model import eligibility_status
2021
from eligibility_signposting_api.model.campaign_config import (
2122
CampaignConfig,
23+
EndDate,
2224
RuleType,
25+
StartDate,
2326
)
2427
from eligibility_signposting_api.repos.campaign_repo import BucketName
2528
from eligibility_signposting_api.repos.person_repo import TableName
@@ -380,7 +383,7 @@ def persisted_person_all_cohorts(person_table: Any, faker: Faker) -> Generator[e
380383
nhs_number,
381384
date_of_birth=date_of_birth,
382385
postcode="SW19",
383-
cohorts=["cohort_label1", "cohort_label2", "cohort_label3", "cohort_label4"],
386+
cohorts=["cohort_label1", "cohort_label2", "cohort_label3", "cohort_label4", "cohort_label5"],
384387
icb="QE1",
385388
).data
386389
):
@@ -484,6 +487,48 @@ def campaign_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generato
484487
s3_client.delete_object(Bucket=rules_bucket, Key=f"{campaign.name}.json")
485488

486489

490+
@pytest.fixture(scope="class")
491+
def inactive_iteration_config(s3_client: BaseClient, rules_bucket: BucketName) -> Generator[list[CampaignConfig]]:
492+
campaigns, campaign_data_keys = [], []
493+
494+
target_iteration_dates = {
495+
"start_date": ("RSV", datetime.date(2025, 1, 1)), # Active Iteration Date
496+
"start_date_plus_one_day": ("COVID", datetime.date(2025, 1, 2)), # Active Iteration Date
497+
"today": ("FLU", datetime.date(2025, 8, 8)), # Active Iteration Date
498+
"tomorrow": ("MMR", datetime.date(2025, 8, 9)), # Inactive Iteration Date
499+
}
500+
501+
for target, data in target_iteration_dates.items():
502+
campaign = rule.CampaignConfigFactory.build(
503+
id=f"campaign_{target}",
504+
target=data[0],
505+
type="V",
506+
iterations=[
507+
rule.IterationFactory.build(
508+
iteration_rules=[rule.PersonAgeSuppressionRuleFactory.build()],
509+
iteration_cohorts=[rule.IterationCohortFactory.build(cohort_label="cohort_label1")],
510+
)
511+
],
512+
)
513+
514+
campaign.start_date = StartDate(datetime.date(2025, 1, 1))
515+
campaign.end_date = EndDate(datetime.date(2026, 1, 1))
516+
campaign.iterations[0].iteration_date = data[1]
517+
518+
campaign_data = {"CampaignConfig": campaign.model_dump(by_alias=True)}
519+
key = f"{campaign.name}.json"
520+
s3_client.put_object(
521+
Bucket=rules_bucket, Key=key, Body=json.dumps(campaign_data), ContentType="application/json"
522+
)
523+
campaigns.append(campaign)
524+
campaign_data_keys.append(key)
525+
526+
yield campaigns
527+
528+
for key in campaign_data_keys:
529+
s3_client.delete_object(Bucket=rules_bucket, Key=key)
530+
531+
487532
@pytest.fixture(scope="class")
488533
def campaign_config_with_and_rule(s3_client: BaseClient, rules_bucket: BucketName) -> Generator[CampaignConfig]:
489534
campaign: CampaignConfig = rule.CampaignConfigFactory.build(

tests/integration/lambda/test_app_running_as_lambda.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from brunns.matchers.data import json_matching as is_json_that
1111
from brunns.matchers.response import is_response
1212
from faker import Faker
13+
from freezegun import freeze_time
1314
from hamcrest import (
1415
assert_that,
1516
contains_exactly,
@@ -539,3 +540,40 @@ def test_given_person_has_unique_status_for_different_conditions_with_audit( #
539540
assert_that(audit_data["response"]["responseId"], is_not(equal_to("")))
540541
assert_that(audit_data["response"]["lastUpdated"], is_not(equal_to("")))
541542
assert_that(audit_data["response"]["condition"], contains_inanyorder(*expected_conditions))
543+
544+
545+
@freeze_time("2025-08-08")
546+
def test_no_active_iteration_returns_empty_processed_suggestions(
547+
lambda_client: BaseClient, # noqa:ARG001
548+
persisted_person_all_cohorts: NHSNumber,
549+
inactive_iteration_config: list[CampaignConfig], # noqa:ARG001
550+
api_gateway_endpoint: URL,
551+
):
552+
invoke_url = f"{api_gateway_endpoint}/patient-check/{persisted_person_all_cohorts}"
553+
response = httpx.get(
554+
invoke_url,
555+
headers={
556+
"nhs-login-nhs-number": str(persisted_person_all_cohorts),
557+
"x_request_id": "x_request_id",
558+
"x_correlation_id": "x_correlation_id",
559+
"nhsd_end_user_organisation_ods": "nhsd_end_user_organisation_ods",
560+
"nhsd_application_id": "nhsd_application_id",
561+
},
562+
params={"includeActions": "Y", "category": "VACCINATIONS", "conditions": "COVID,FLU,RSV"},
563+
timeout=10,
564+
)
565+
566+
assert_that(
567+
response,
568+
is_response().with_status_code(HTTPStatus.OK).and_body(is_json_that(has_key("processedSuggestions"))),
569+
)
570+
571+
body = response.json()
572+
assert_that(
573+
body["processedSuggestions"],
574+
contains_inanyorder(
575+
has_entries("condition", "COVID"),
576+
has_entries("condition", "RSV"),
577+
has_entries("condition", "FLU"),
578+
),
579+
)

tests/unit/model/test_campaign_config.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,6 @@ def test_iteration_with_overlapping_start_dates_not_allowed(faker: Faker):
5252
RawCampaignConfigFactory.build(start_date=start_date, iterations=[iteration1, iteration2])
5353

5454

55-
def test_iteration_must_have_active_iteration_from_its_start(faker: Faker):
56-
# Given
57-
start_date = faker.date_object()
58-
iteration = IterationFactory.build(iteration_date=start_date + relativedelta(days=1))
59-
60-
# When, Then
61-
with pytest.raises(
62-
ValueError,
63-
match=r"1 validation error for CampaignConfig\n"
64-
r".*1st iteration starts later",
65-
):
66-
RawCampaignConfigFactory.build(start_date=start_date, iterations=[iteration])
67-
68-
6955
@pytest.mark.parametrize(
7056
("rule_stop", "expected"),
7157
[

0 commit comments

Comments
 (0)