"""Unit tests for jira_query_offboarding_tickets.utils module.""" import pytest from jira_client import RegexPatterns, SearchUtils, TextUtils @pytest.mark.parametrize( 'raw,expected', [ ('Doe.Please', 'Doe. Please'), ('Doe,Please', 'Doe, Please'), ('703101Title', '703101 Title'), ('MarketingEmail', 'Marketing Email'), ('UTCLitigation', 'UTC Litigation'), ('Hello World', 'Hello World'), ('Nochange', 'Nochange'), ], ) def test_clean_text_patterns(raw: str, expected: str) -> None: """Each individual regex replacement in TextUtils.clean_text. :param raw: :param expected: :return: """ assert TextUtils.clean_text(raw) == expected def test_clean_text_combined() -> None: """All patterns applied sequentially on a complex string. :return: """ raw = 'Doe.Please,703101MarketingEmail UTCLitigation' cleaned = TextUtils.clean_text(raw) expected = 'Doe. Please,703101 Marketing Email UTC Litigation' assert cleaned == expected def test_clean_text_whitespace_normalization() -> None: """Whitespace collapse and trimming. :return: """ assert TextUtils.clean_text(' Hello World ') == 'Hello World' assert TextUtils.clean_text('Hello\tWorld') == 'Hello World' @pytest.mark.parametrize( 'text,expected', [ ('No emails here', []), ('John Doe ', ['john.doe@example.com']), ( 'Multiple emails: john@example.com, jane_doe123@test.co.uk', ['john@example.com', 'jane_doe123@test.co.uk'], ), ('Edge case: email@sub.domain.com', ['email@sub.domain.com']), ], ) def test_find_all_emails(text: str, expected: str) -> None: """SearchUtils.find_all_emails extracts all email addresses. :param text: :param expected: :return: """ assert SearchUtils.find_all_emails(text) == expected @pytest.mark.parametrize( 'text,expected', [ ('request has been submitted for John Doe.', 'John Doe'), ('request has been submitted for Jane Smith.', 'Jane Smith'), ('Request has been submitted for John Doe.', None), # case‑sensitive ('request has been submitted forJohn Doe.', None), # missing space ('Some random text', None), # Middle initial — last name must NOT be dropped ('request has been submitted for John A. Doe.', 'John A. Doe'), ('request has been submitted for Mary B. Johnson.', 'Mary B. Johnson'), ('request has been submitted for John-Doe.', 'John-Doe'), # Unicode letters — accented characters must not break name boundary detection ('request has been submitted for Eduardo España.', 'Eduardo España'), ('request has been submitted for José García.', 'José García'), ( 'Some text request has been submitted for Alice Bob. More text', 'Alice Bob', ), ], ) def test_find_full_name(text: str, expected: str) -> None: """SearchUtils.find_full_name returns the first matched name or None. :param text: :param expected: """ assert ( SearchUtils.find_full_name(text, RegexPatterns.OFFBOARDING_FULL_NAME_RE) == expected ) @pytest.mark.parametrize( 'text,expected', [ # Real suspend phrasing: "submitted for ." then later "User ID:". ( 'A suspend network access request has been submitted for John Doe. ' 'Please ensure all application access is removed. User ID: JD001', 'John Doe', ), ( 'request has been submitted for Jane Smith. User ID: JS9', 'Jane Smith', ), ( 'request has been submitted for Eduardo España. User ID: EE1', 'Eduardo España', ), # Middle initial — last name must NOT be dropped. ('request has been submitted for John A. Doe. User ID: JD2', 'John A. Doe'), ('Request has been submitted for John Doe.', None), # case-sensitive ('request has been submitted forJohn Doe.', None), # missing space ('Some random text', None), ], ) def test_find_full_name_suspend(text: str, expected: str | None) -> None: """SearchUtils.find_full_name with the suspend pattern stops at the period. Suspend tickets use the same "submitted for ." phrasing as offboarding tickets, so the capture terminates at the first period. :param text: :param expected: """ assert ( SearchUtils.find_full_name(text, RegexPatterns.SUSPEND_FULL_NAME_RE) == expected ) @pytest.mark.parametrize( 'text,expected', [ ('Last day of Employment: 2026-01-30', '2026-01-30'), ('No date here', None), ], ) def test_find_last_working_day(text: str, expected: str | None) -> None: """SearchUtils.find_last_working_day extracts the ISO date or None. :param text: :param expected: """ assert SearchUtils.find_last_working_day(text) == expected @pytest.mark.parametrize( 'text,expected', [ ('Suspension Start Date: 2026-03-16', '2026-03-16'), ('No date here', None), # The offboarding label must not be picked up here. ('Last day of Employment: 2026-01-30', None), ], ) def test_find_suspension_start_date(text: str, expected: str | None) -> None: """SearchUtils.find_suspension_start_date extracts the ISO date or None. :param text: :param expected: """ assert SearchUtils.find_suspension_start_date(text) == expected