Skip to content

Commit cd5cd06

Browse files
committed
eli-579 switched tests to using hamcrest matchers, to match other test suites
1 parent 55d9f86 commit cd5cd06

3 files changed

Lines changed: 109 additions & 75 deletions

File tree

tests/unit/services/processors/test_token_parser.py

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import pytest
2+
from hamcrest import assert_that, calling, equal_to, is_, raises
23

34
from eligibility_signposting_api.services.processors.token_parser import TokenParser
45

@@ -21,10 +22,10 @@ class TestTokenParser:
2122
)
2223
def test_parse_valid_tokens(self, token, expected_level, expected_name, expected_value, expected_format):
2324
parsed_token = TokenParser.parse(token)
24-
assert parsed_token.attribute_level == expected_level
25-
assert parsed_token.attribute_name == expected_name
26-
assert parsed_token.attribute_value == expected_value
27-
assert parsed_token.format == expected_format
25+
assert_that(parsed_token.attribute_level, is_(equal_to(expected_level)))
26+
assert_that(parsed_token.attribute_name, is_(equal_to(expected_name)))
27+
assert_that(parsed_token.attribute_value, is_(equal_to(expected_value)))
28+
assert_that(parsed_token.format, is_(equal_to(expected_format)))
2829

2930
@pytest.mark.parametrize(
3031
"token",
@@ -38,8 +39,10 @@ def test_parse_valid_tokens(self, token, expected_level, expected_name, expected
3839
],
3940
)
4041
def test_parse_invalid_tokens_raises_error(self, token):
41-
with pytest.raises(ValueError, match=r"Invalid token\."):
42-
TokenParser.parse(token)
42+
assert_that(
43+
calling(TokenParser.parse).with_args(token),
44+
raises(ValueError, pattern=r"Invalid token\."),
45+
)
4346

4447
@pytest.mark.parametrize(
4548
"token",
@@ -52,12 +55,14 @@ def test_parse_invalid_tokens_raises_error(self, token):
5255
],
5356
)
5457
def test_parse_invalid_token_format_raises_error(self, token):
55-
with pytest.raises(ValueError, match=r"Invalid token format\."):
56-
TokenParser.parse(token)
58+
assert_that(
59+
calling(TokenParser.parse).with_args(token),
60+
raises(ValueError, pattern=r"Invalid token format\."),
61+
)
5762

5863
def test_parse_function_token_valid(self):
5964
"""Test that valid function tokens are parsed correctly."""
6065
# This used to be invalid, but now we support custom functions
6166
parsed = TokenParser.parse("[[PERSON.DATE_OF_BIRTH:SOME_FUNC(abc)]]")
62-
assert parsed.function_name == "SOME_FUNC"
63-
assert parsed.function_args == "abc"
67+
assert_that(parsed.function_name, is_(equal_to("SOME_FUNC")))
68+
assert_that(parsed.function_args, is_(equal_to("abc")))

tests/unit/services/processors/test_token_processor.py

Lines changed: 74 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import re
22

33
import pytest
4+
from hamcrest import assert_that, calling, equal_to, is_, raises
45

56
from eligibility_signposting_api.model import eligibility_status
67
from eligibility_signposting_api.model.eligibility_status import (
@@ -43,7 +44,7 @@ def test_simple_token_replacement(self):
4344

4445
actual = TokenProcessor.find_and_replace_tokens(person, condition)
4546

46-
assert actual == expected
47+
assert_that(actual, is_(equal_to(expected)))
4748

4849
def test_deep_nesting_token_replacement(self):
4950
person = Person([{"ATTRIBUTE_TYPE": "PERSON", "AGE": "30", "DEGREE": "DOCTOR", "QUALITY": "NICE"}])
@@ -84,9 +85,9 @@ def test_deep_nesting_token_replacement(self):
8485

8586
actual = TokenProcessor.find_and_replace_tokens(person, condition)
8687

87-
assert actual.cohort_results[0].description == "Results for cohort 30."
88-
assert actual.cohort_results[0].reasons[1].rule_text == "Rule 30 here."
89-
assert actual.status_text == StatusText("Everything is NICE.")
88+
assert_that(actual.cohort_results[0].description, is_(equal_to("Results for cohort 30.")))
89+
assert_that(actual.cohort_results[0].reasons[1].rule_text, is_(equal_to("Rule 30 here.")))
90+
assert_that(actual.status_text, is_(equal_to(StatusText("Everything is NICE."))))
9091

9192
def test_invalid_token_on_person_attribute_should_raise_error(self):
9293
person = Person([{"ATTRIBUTE_TYPE": "PERSON", "AGE": "30"}])
@@ -102,8 +103,10 @@ def test_invalid_token_on_person_attribute_should_raise_error(self):
102103

103104
expected_error = re.escape("Invalid attribute name 'ICECREAM' in token '[[PERSON.ICECREAM]]'.")
104105

105-
with pytest.raises(ValueError, match=expected_error):
106-
TokenProcessor.find_and_replace_tokens(person, condition)
106+
assert_that(
107+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
108+
raises(ValueError, pattern=expected_error),
109+
)
107110

108111
def test_invalid_token_should_raise_error(self):
109112
person = Person([{"ATTRIBUTE_TYPE": "PERSON", "AGE": "30"}])
@@ -118,8 +121,10 @@ def test_invalid_token_should_raise_error(self):
118121
)
119122

120123
expected_error = re.escape("Invalid attribute level 'ICECREAM' in token '[[ICECREAM.FLAVOR]]'.")
121-
with pytest.raises(ValueError, match=expected_error):
122-
TokenProcessor.find_and_replace_tokens(person, condition)
124+
assert_that(
125+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
126+
raises(ValueError, pattern=expected_error),
127+
)
123128

124129
def test_invalid_token_on_target_attribute_should_raise_error(self):
125130
person = Person([{"ATTRIBUTE_TYPE": "RSV", "LAST_SUCCESSFUL_DATE": "20250101"}])
@@ -134,8 +139,10 @@ def test_invalid_token_on_target_attribute_should_raise_error(self):
134139
)
135140

136141
expected_error = re.escape("Invalid attribute name 'ICECREAM' in token '[[TARGET.RSV.ICECREAM]]'.")
137-
with pytest.raises(ValueError, match=expected_error):
138-
TokenProcessor.find_and_replace_tokens(person, condition)
142+
assert_that(
143+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
144+
raises(ValueError, pattern=expected_error),
145+
)
139146

140147
def test_missing_target_attribute_and_invalid_token_should_raise_error(self):
141148
person = Person([{"ATTRIBUTE_TYPE": "PERSON", "AGE": "30"}])
@@ -150,8 +157,10 @@ def test_missing_target_attribute_and_invalid_token_should_raise_error(self):
150157
)
151158

152159
expected_error = re.escape("Invalid attribute name 'ICECREAM' in token '[[TARGET.RSV.ICECREAM]]'.")
153-
with pytest.raises(ValueError, match=expected_error):
154-
TokenProcessor.find_and_replace_tokens(person, condition)
160+
assert_that(
161+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
162+
raises(ValueError, pattern=expected_error),
163+
)
155164

156165
def test_missing_patient_vaccine_data_on_target_attribute_should_replace_with_empty(self):
157166
person = Person([{"ATTRIBUTE_TYPE": "PERSON", "AGE": "30"}])
@@ -167,7 +176,7 @@ def test_missing_patient_vaccine_data_on_target_attribute_should_replace_with_em
167176

168177
actual = TokenProcessor.find_and_replace_tokens(person, condition)
169178

170-
assert actual.condition_name == "Last successful date: "
179+
assert_that(actual.condition_name, is_(equal_to("Last successful date: ")))
171180

172181
def test_not_allowed_target_conditions_token_should_raise_error(self):
173182
person = Person(
@@ -189,8 +198,10 @@ def test_not_allowed_target_conditions_token_should_raise_error(self):
189198
expected_error = re.escape(
190199
"Invalid attribute name 'LAST_SUCCESSFUL_DATE' in token '[[TARGET.YELLOW_FEVER.LAST_SUCCESSFUL_DATE]]'."
191200
)
192-
with pytest.raises(ValueError, match=expected_error):
193-
TokenProcessor.find_and_replace_tokens(person, condition)
201+
assert_that(
202+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
203+
raises(ValueError, pattern=expected_error),
204+
)
194205

195206
def test_valid_token_but_missing_attribute_data_to_replace(self):
196207
person = Person(
@@ -223,8 +234,8 @@ def test_valid_token_but_missing_attribute_data_to_replace(self):
223234

224235
actual = TokenProcessor.find_and_replace_tokens(person, condition)
225236

226-
assert actual.status_text == expected.status_text
227-
assert actual.condition_name == expected.condition_name
237+
assert_that(actual.status_text, is_(equal_to(expected.status_text)))
238+
assert_that(actual.condition_name, is_(equal_to(expected.condition_name)))
228239

229240
def test_valid_token_but_missing_attribute_in_multiple_vacc_data_to_replace(self):
230241
person = Person(
@@ -259,8 +270,8 @@ def test_valid_token_but_missing_attribute_in_multiple_vacc_data_to_replace(self
259270

260271
actual = TokenProcessor.find_and_replace_tokens(person, condition)
261272

262-
assert actual.status_text == expected.status_text
263-
assert actual.condition_name == expected.condition_name
273+
assert_that(actual.status_text, is_(equal_to(expected.status_text)))
274+
assert_that(actual.condition_name, is_(equal_to(expected.condition_name)))
264275

265276
def test_simple_string_with_multiple_tokens(self):
266277
person = Person(
@@ -292,7 +303,7 @@ def test_simple_string_with_multiple_tokens(self):
292303

293304
actual = TokenProcessor.find_and_replace_tokens(person, condition)
294305

295-
assert actual == expected
306+
assert_that(actual, is_(equal_to(expected)))
296307

297308
def test_valid_token_valid_format_should_replace_with_date_formatting(self):
298309
person = Person(
@@ -325,8 +336,8 @@ def test_valid_token_valid_format_should_replace_with_date_formatting(self):
325336

326337
actual = TokenProcessor.find_and_replace_tokens(person, condition)
327338

328-
assert actual.condition_name == expected.condition_name
329-
assert actual.status_text == expected.status_text
339+
assert_that(actual.condition_name, is_(equal_to(expected.condition_name)))
340+
assert_that(actual.status_text, is_(equal_to(expected.status_text)))
330341

331342
@pytest.mark.parametrize(
332343
"token_format",
@@ -348,8 +359,10 @@ def test_valid_token_invalid_format_should_raise_error(self, token_format: str):
348359
actions=[],
349360
)
350361

351-
with pytest.raises(ValueError, match=r"Invalid token format\."):
352-
TokenProcessor.find_and_replace_tokens(person, condition)
362+
assert_that(
363+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
364+
raises(ValueError, pattern=r"Invalid token format\."),
365+
)
353366

354367
@pytest.mark.parametrize(
355368
("token_format", "func_name"),
@@ -373,8 +386,10 @@ def test_unknown_function_raises_error(self, token_format: str, func_name: str):
373386
actions=[],
374387
)
375388

376-
with pytest.raises(ValueError, match=f"Unknown function '{func_name}'"):
377-
TokenProcessor.find_and_replace_tokens(person, condition)
389+
assert_that(
390+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
391+
raises(ValueError, pattern=f"Unknown function '{func_name}'"),
392+
)
378393

379394
@pytest.mark.parametrize(
380395
("token_format", "expected"),
@@ -409,7 +424,7 @@ def test_valid_date_format(self, token_format: str, expected: str):
409424

410425
actual = TokenProcessor.find_and_replace_tokens(person, condition)
411426

412-
assert actual.condition_name == f"Date: {expected}"
427+
assert_that(actual.condition_name, is_(equal_to(f"Date: {expected}")))
413428

414429
@pytest.mark.parametrize(
415430
("token", "expected"),
@@ -443,8 +458,8 @@ def test_token_replace_is_case_insensitive(self, token: str, expected: str):
443458

444459
result = TokenProcessor.find_and_replace_tokens(person, condition)
445460

446-
assert result.status_text == f"Your DOB is: {expected}."
447-
assert result.condition_name == f"FLU vaccine on: {expected}."
461+
assert_that(result.status_text, is_(equal_to(f"Your DOB is: {expected}.")))
462+
assert_that(result.condition_name, is_(equal_to(f"FLU vaccine on: {expected}.")))
448463

449464

450465
class TestCustomTargetAttributeNames:
@@ -472,7 +487,7 @@ def test_custom_target_attribute_with_add_days_when_data_present(self):
472487
result = TokenProcessor.find_and_replace_tokens(person, condition)
473488

474489
# 2026-01-28 + 71 days = 2026-04-09
475-
assert result.status_text == "Next booking: 20260409"
490+
assert_that(result.status_text, is_(equal_to("Next booking: 20260409")))
476491

477492
def test_custom_target_attribute_with_add_days_and_formatting(self):
478493
"""Test that custom target attributes work with both ADD_DAYS and DATE formatting."""
@@ -496,7 +511,7 @@ def test_custom_target_attribute_with_add_days_and_formatting(self):
496511
result = TokenProcessor.find_and_replace_tokens(person, condition)
497512

498513
# 2026-01-28 + 71 days = 2026-04-09, formatted as "09 April 2026"
499-
assert result.status_text == "Date: 09 April 2026"
514+
assert_that(result.status_text, is_(equal_to("Date: 09 April 2026")))
500515

501516
def test_custom_target_attribute_returns_empty_when_condition_not_present(self):
502517
"""Test that custom target attributes return empty string when condition data not present."""
@@ -521,7 +536,7 @@ def test_custom_target_attribute_returns_empty_when_condition_not_present(self):
521536
result = TokenProcessor.find_and_replace_tokens(person, condition)
522537

523538
# Should return empty string when condition data is not present
524-
assert result.status_text == "Next booking: "
539+
assert_that(result.status_text, is_(equal_to("Next booking: ")))
525540

526541
def test_multiple_custom_target_attributes_with_different_functions(self):
527542
"""Test multiple custom target attributes with different parameters."""
@@ -546,7 +561,7 @@ def test_multiple_custom_target_attributes_with_different_functions(self):
546561
result = TokenProcessor.find_and_replace_tokens(person, condition)
547562

548563
# 2026-01-28 + 30 = 2026-02-27, + 60 = 2026-03-29
549-
assert result.status_text == "First: 20260227 Second: 20260329"
564+
assert_that(result.status_text, is_(equal_to("First: 20260227 Second: 20260329")))
550565

551566
def test_custom_target_attribute_raises_error_for_invalid_condition(self):
552567
"""Test that invalid condition names still raise errors even with custom target attributes."""
@@ -565,8 +580,10 @@ def test_custom_target_attribute_raises_error_for_invalid_condition(self):
565580
actions=[],
566581
)
567582

568-
with pytest.raises(ValueError, match="Invalid attribute name 'CUSTOM_FIELD'"):
569-
TokenProcessor.find_and_replace_tokens(person, condition)
583+
assert_that(
584+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
585+
raises(ValueError, pattern="Invalid attribute name 'CUSTOM_FIELD'"),
586+
)
570587

571588
def test_non_derived_token_with_invalid_target_attribute_raises_error(self):
572589
"""Test that non-derived tokens (without functions) validate target attributes strictly."""
@@ -586,8 +603,10 @@ def test_non_derived_token_with_invalid_target_attribute_raises_error(self):
586603
)
587604

588605
# Non-derived tokens should only allow ALLOWED_TARGET_ATTRIBUTES
589-
with pytest.raises(ValueError, match="Invalid attribute name 'CUSTOM_INVALID_FIELD'"):
590-
TokenProcessor.find_and_replace_tokens(person, condition)
606+
assert_that(
607+
calling(TokenProcessor.find_and_replace_tokens).with_args(person, condition),
608+
raises(ValueError, pattern="Invalid attribute name 'CUSTOM_INVALID_FIELD'"),
609+
)
591610

592611
def test_non_derived_token_with_valid_target_attribute_works(self):
593612
"""Test that non-derived tokens with valid target attributes work correctly."""
@@ -608,7 +627,7 @@ def test_non_derived_token_with_valid_target_attribute_works(self):
608627

609628
result = TokenProcessor.find_and_replace_tokens(person, condition)
610629

611-
assert result.status_text == "Last date: 20260128"
630+
assert_that(result.status_text, is_(equal_to("Last date: 20260128")))
612631

613632
def test_person_level_attribute_with_add_days_without_explicit_source(self):
614633
"""Test that ADD_DAYS works on PERSON-level attributes without explicit source."""
@@ -630,7 +649,7 @@ def test_person_level_attribute_with_add_days_without_explicit_source(self):
630649
result = TokenProcessor.find_and_replace_tokens(person, condition)
631650

632651
# 1990-03-27 + 91 days = 1990-06-26
633-
assert result.status_text == "Future date: 19900626"
652+
assert_that(result.status_text, is_(equal_to("Future date: 19900626")))
634653

635654
def test_person_level_attribute_with_add_days_explicit_source(self):
636655
"""Test that ADD_DAYS works on PERSON-level attributes with explicit source."""
@@ -652,7 +671,7 @@ def test_person_level_attribute_with_add_days_explicit_source(self):
652671
result = TokenProcessor.find_and_replace_tokens(person, condition)
653672

654673
# 1990-03-27 + 91 days = 1990-06-26
655-
assert result.status_text == "Future date: 19900626"
674+
assert_that(result.status_text, is_(equal_to("Future date: 19900626")))
656675

657676
def test_derived_value_with_no_function_name_raises_error(self):
658677
"""Test that derived tokens without function name raise ValueError."""
@@ -666,13 +685,15 @@ def test_derived_value_with_no_function_name_raises_error(self):
666685
format=None,
667686
)
668687

669-
with pytest.raises(ValueError, match="No function specified in token"):
670-
TokenProcessor.get_derived_value(
688+
assert_that(
689+
calling(TokenProcessor.get_derived_value).with_args(
671690
parsed_token=parsed_token,
672691
person_data=[{"ATTRIBUTE_TYPE": "COVID", "LAST_SUCCESSFUL_DATE": "20250101"}],
673692
present_attributes={"COVID"},
674693
token="[[TARGET.COVID.NEXT_DOSE_DUE:]]", # Malformed token
675-
)
694+
),
695+
raises(ValueError, pattern="No function specified in token"),
696+
)
676697

677698
def test_derived_value_with_unknown_function_raises_error(self):
678699
"""Test that derived tokens with unknown function raise ValueError."""
@@ -685,13 +706,15 @@ def test_derived_value_with_unknown_function_raises_error(self):
685706
format=None,
686707
)
687708

688-
with pytest.raises(ValueError, match="Unknown function 'UNKNOWN_FUNCTION' in token"):
689-
TokenProcessor.get_derived_value(
709+
assert_that(
710+
calling(TokenProcessor.get_derived_value).with_args(
690711
parsed_token=parsed_token,
691712
person_data=[{"ATTRIBUTE_TYPE": "COVID", "LAST_SUCCESSFUL_DATE": "20250101"}],
692713
present_attributes={"COVID"},
693714
token="[[TARGET.COVID.NEXT_DOSE_DUE:UNKNOWN_FUNCTION(30)]]",
694-
)
715+
),
716+
raises(ValueError, pattern="Unknown function 'UNKNOWN_FUNCTION' in token"),
717+
)
695718

696719
def test_derived_value_handler_exception_gets_wrapped(self):
697720
"""Test that exceptions from derived value handlers are wrapped with context."""
@@ -704,10 +727,12 @@ def test_derived_value_handler_exception_gets_wrapped(self):
704727
format=None,
705728
)
706729

707-
with pytest.raises(ValueError, match=r"Error calculating derived value for token.*Invalid days argument"):
708-
TokenProcessor.get_derived_value(
730+
assert_that(
731+
calling(TokenProcessor.get_derived_value).with_args(
709732
parsed_token=parsed_token,
710733
person_data=[{"ATTRIBUTE_TYPE": "COVID", "LAST_SUCCESSFUL_DATE": "20250101"}],
711734
present_attributes={"COVID"},
712735
token="[[TARGET.COVID.NEXT_DOSE_DUE:ADD_DAYS(invalid_arg)]]",
713-
)
736+
),
737+
raises(ValueError, pattern=r"Error calculating derived value for token.*Invalid days argument"),
738+
)

0 commit comments

Comments
 (0)