Skip to content

Commit eb8a1ed

Browse files
authored
Merge branch 'main' into feature/ELI-731-add-regression-role-to-tf
2 parents 1e00ee3 + df56379 commit eb8a1ed

3 files changed

Lines changed: 193 additions & 50 deletions

File tree

.github/workflows/regression-tests.yml

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,56 +9,17 @@ on:
99
VERSION_NUMBER:
1010
required: true
1111
type: string
12-
secrets: { }
1312

1413
jobs:
1514
regression-tests:
16-
runs-on: ubuntu-22.04
17-
steps:
18-
- name: Checkout
19-
uses: actions/checkout@v6
20-
with:
21-
fetch-depth: 0
22-
23-
- name: Cache asdf
24-
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb
25-
with:
26-
path: |
27-
~/.asdf
28-
key: ${{ runner.os }}-asdf-${{ hashFiles('**/.tool-versions') }}
29-
restore-keys: |
30-
${{ runner.os }}-asdf-
31-
32-
- name: Install asdf tools
33-
uses: asdf-vm/actions/install@b7bcd026f18772e44fe1026d729e1611cc435d47
34-
with:
35-
asdf_branch: v0.15.0
36-
env:
37-
PYTHON_CONFIGURE_OPTS: --enable-shared
38-
39-
- name: Derive Python version from .tool-versions
40-
run: |
41-
PYTHON_VERSION=$(grep "^python" .tool-versions | sed 's/python //g')
42-
echo "PYTHON_VERSION=$PYTHON_VERSION" >> $GITHUB_ENV
43-
44-
- name: setup python venv
45-
uses: actions/checkout@v6
46-
- uses: actions/setup-python@v6
47-
with:
48-
python-version: '${{ env.PYTHON_VERSION }}'
49-
cache: 'poetry' # caching poetry dependencies
50-
- run: poetry install
51-
52-
- name: Run Regression Testing
53-
working-directory: scripts
54-
if: ${{ (inputs.ENVIRONMENT != 'prod') && (inputs.ENVIRONMENT != 'ref') }}
55-
env:
56-
TARGET_ENVIRONMENT: ${{ inputs.ENVIRONMENT }}
57-
VERSION_NUMBER: ${{ inputs.VERSION_NUMBER }}
58-
TESTS_TOKEN: ${{ secrets.REGRESSION_TESTS_PAT }}
59-
run: |
60-
echo Running regression tests in the "$TARGET_ENVIRONMENT" environment.
61-
poetry run python run_regression_tests.py \
62-
--env="$TARGET_ENVIRONMENT" \
63-
--token="$TESTS_TOKEN" \
64-
--regression_test_repo_tag "$VERSION_NUMBER"
15+
if: ${{ inputs.ENVIRONMENT != 'prod' }}
16+
permissions:
17+
id-token: write
18+
contents: read
19+
issues: write
20+
pull-requests: write
21+
uses: NHSDigital/eligibility-signposting-api-regression-tests/.github/workflows/regression_tests.yml@main
22+
with:
23+
environment: ${{ inputs.ENVIRONMENT }}
24+
tags: "@regression"
25+
github_tag: ${{ inputs.VERSION_NUMBER }}

src/eligibility_signposting_api/services/calculators/eligibility_calculator.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ def get_eligibility_status(
102102
include_actions_flag=include_actions_flag,
103103
)
104104

105+
if matched_action_detail.status_text_override:
106+
iteration_result_summary.iteration_result.status_text = matched_action_detail.status_text_override
107+
105108
iteration_result_summary = TokenProcessor.find_and_replace_tokens(self.person, iteration_result_summary)
106109
matched_action_detail = TokenProcessor.find_and_replace_tokens(self.person, matched_action_detail)
107110

tests/unit/services/calculators/test_eligibility_calculator.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22
import logging
33
from typing import Any
4+
from unittest.mock import MagicMock
45

56
import pytest
67
from faker import Faker
@@ -33,6 +34,7 @@
3334
DateOfBirth,
3435
InternalActionCode,
3536
IterationResult,
37+
MatchedActionDetail,
3638
NHSNumber,
3739
Postcode,
3840
Reason,
@@ -53,6 +55,183 @@
5355
)
5456

5557

58+
def test_get_eligibility_status_uses_status_text_override_when_present(faker: Faker):
59+
"""
60+
Test: override present → statusText replaced
61+
If an override status text is provided, it replaces the original status text
62+
produced by the iteration.
63+
"""
64+
nhs_number = NHSNumber(faker.nhs_number())
65+
66+
person_rows = person_rows_builder(
67+
nhs_number,
68+
cohorts=["cohort1"],
69+
icb="QE1",
70+
)
71+
72+
campaign_configs = [
73+
rule_builder.CampaignConfigFactory.build(
74+
target="RSV",
75+
iterations=[
76+
rule_builder.IterationFactory.build(
77+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
78+
iteration_rules=[],
79+
status_text=campaign_config.StatusText(
80+
NotEligible="You are not eligible for RSV.",
81+
NotActionable="You are not currently able to book RSV.",
82+
Actionable="Original actionable text",
83+
),
84+
)
85+
],
86+
)
87+
]
88+
89+
calculator = EligibilityCalculator(person_rows, campaign_configs)
90+
calculator.action_rule_handler.get_actions = MagicMock(
91+
return_value=MatchedActionDetail(status_text_override=StatusText("Override actionable text"))
92+
)
93+
94+
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")
95+
96+
assert len(actual.conditions) == 1
97+
assert actual.conditions[0].condition_name == ConditionName("RSV")
98+
assert actual.conditions[0].status == Status.actionable
99+
assert actual.conditions[0].status_text == StatusText("Override actionable text")
100+
assert g.audit_log.response.condition[0].status_text == "Override actionable text"
101+
102+
103+
def test_get_eligibility_status_keeps_original_status_text_when_override_absent(faker: Faker):
104+
"""
105+
Override absent → statusText unchanged
106+
If no override is provided, the calculator should keep the original
107+
status text from the iteration.
108+
"""
109+
nhs_number = NHSNumber(faker.nhs_number())
110+
111+
person_rows = person_rows_builder(
112+
nhs_number,
113+
cohorts=["cohort1"],
114+
icb="QE1",
115+
)
116+
117+
campaign_configs = [
118+
rule_builder.CampaignConfigFactory.build(
119+
target="RSV",
120+
iterations=[
121+
rule_builder.IterationFactory.build(
122+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
123+
iteration_rules=[],
124+
status_text=campaign_config.StatusText(
125+
NotEligible="You are not eligible for RSV.",
126+
NotActionable="You are not currently able to book RSV.",
127+
Actionable="Original actionable text",
128+
),
129+
)
130+
],
131+
)
132+
]
133+
134+
calculator = EligibilityCalculator(person_rows, campaign_configs)
135+
calculator.action_rule_handler.get_actions = MagicMock(return_value=MatchedActionDetail(status_text_override=None))
136+
137+
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")
138+
139+
assert len(actual.conditions) == 1
140+
assert actual.conditions[0].condition_name == ConditionName("RSV")
141+
assert actual.conditions[0].status == Status.actionable
142+
assert actual.conditions[0].status_text == StatusText("Original actionable text")
143+
assert g.audit_log.response.condition[0].status_text == "Original actionable text"
144+
145+
146+
def test_get_eligibility_status_resolves_tokens_in_status_text_override(faker: Faker):
147+
"""
148+
Test: override with token → resolved
149+
check that a token inside the override status text gets replaced with actual person data.
150+
"""
151+
nhs_number = NHSNumber(faker.nhs_number())
152+
153+
person_rows = person_rows_builder(
154+
nhs_number,
155+
cohorts=["cohort1"],
156+
icb="ICB123",
157+
)
158+
159+
campaign_configs = [
160+
rule_builder.CampaignConfigFactory.build(
161+
target="RSV",
162+
iterations=[
163+
rule_builder.IterationFactory.build(
164+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
165+
iteration_rules=[],
166+
status_text=campaign_config.StatusText(
167+
NotEligible="You are not eligible for RSV.",
168+
NotActionable="You are not currently able to book RSV.",
169+
Actionable="Original actionable text",
170+
),
171+
)
172+
],
173+
)
174+
]
175+
176+
calculator = EligibilityCalculator(person_rows, campaign_configs)
177+
calculator.action_rule_handler.get_actions = MagicMock(
178+
return_value=MatchedActionDetail(status_text_override=StatusText("You can book via [[PERSON.ICB]]."))
179+
)
180+
181+
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")
182+
183+
assert len(actual.conditions) == 1
184+
assert actual.conditions[0].condition_name == ConditionName("RSV")
185+
assert actual.conditions[0].status == Status.actionable
186+
assert actual.conditions[0].status_text == StatusText("You can book via ICB123.")
187+
assert g.audit_log.response.condition[0].status_text == "You can book via ICB123."
188+
189+
190+
def test_get_eligibility_status_applies_override_before_token_processing(faker: Faker):
191+
"""
192+
Test: override applied before token processing (order verification)
193+
Check if apply the override first, and only then replace tokens
194+
"""
195+
nhs_number = NHSNumber(faker.nhs_number())
196+
197+
person_rows = person_rows_builder(
198+
nhs_number,
199+
cohorts=["cohort1"],
200+
icb="ICB999",
201+
)
202+
203+
campaign_configs = [
204+
rule_builder.CampaignConfigFactory.build(
205+
target="RSV",
206+
iterations=[
207+
rule_builder.IterationFactory.build(
208+
iteration_cohorts=[rule_builder.IterationCohortFactory.build(cohort_label="cohort1")],
209+
iteration_rules=[],
210+
status_text=campaign_config.StatusText(
211+
NotEligible="You are not eligible for RSV.",
212+
NotActionable="You are not currently able to book RSV.",
213+
Actionable="ORIGINAL [[PERSON.ICB]]",
214+
),
215+
)
216+
],
217+
)
218+
]
219+
220+
calculator = EligibilityCalculator(person_rows, campaign_configs)
221+
calculator.action_rule_handler.get_actions = MagicMock(
222+
return_value=MatchedActionDetail(status_text_override=StatusText("OVERRIDE [[PERSON.ICB]]"))
223+
)
224+
225+
actual = calculator.get_eligibility_status("Y", ["ALL"], "ALL")
226+
227+
assert len(actual.conditions) == 1
228+
assert actual.conditions[0].condition_name == ConditionName("RSV")
229+
assert actual.conditions[0].status == Status.actionable
230+
assert actual.conditions[0].status_text == StatusText("OVERRIDE ICB999")
231+
assert actual.conditions[0].status_text != StatusText("ORIGINAL ICB999")
232+
assert g.audit_log.response.condition[0].status_text == "OVERRIDE ICB999"
233+
234+
56235
@pytest.fixture
57236
def app():
58237
return Flask(__name__)

0 commit comments

Comments
 (0)