diff --git a/legal-api/src/legal_api/services/filings/validations/agm_location_change.py b/legal-api/src/legal_api/services/filings/validations/agm_location_change.py index bb99f2c87a..bf3ea374a2 100644 --- a/legal-api/src/legal_api/services/filings/validations/agm_location_change.py +++ b/legal-api/src/legal_api/services/filings/validations/agm_location_change.py @@ -41,10 +41,16 @@ def validate(business: Business, filing: dict) -> Error | None: agm_year_path: Final = "/filing/agmLocationChange/year" year = get_int(filing, agm_year_path) if year: - expected_min = LegislationDatetime.now().year - 2 - expected_max = LegislationDatetime.now().year + 1 + current_year = LegislationDatetime.now().year + # AGM year can never be before the year the business was founded, even if that is + # more recent than the default 2-year lookback window. + founding_year = LegislationDatetime.as_legislation_timezone(business.founding_date).year \ + if business.founding_date else current_year - 2 + expected_min = max(current_year - 2, founding_year) + expected_max = current_year + 1 if expected_min > year or year > expected_max: - msg.append({"error": "AGM year must be between -2 or +1 year from current year.", "path": agm_year_path}) + msg.append({"error": f"AGM year must be between {expected_min} and {expected_max}.", + "path": agm_year_path}) # A non-empty reason (at least one non-whitespace character) is enforced by the schema # (business-schemas agm_location_change reason pattern). diff --git a/legal-api/tests/unit/services/filings/validations/test_agm_location_change.py b/legal-api/tests/unit/services/filings/validations/test_agm_location_change.py index 2793eeeb95..c08952b404 100644 --- a/legal-api/tests/unit/services/filings/validations/test_agm_location_change.py +++ b/legal-api/tests/unit/services/filings/validations/test_agm_location_change.py @@ -28,41 +28,48 @@ # rejected by schema validation (HTTP 422) instead of the legal-api business check (HTTP 400). SCHEMA_REJECTED = 'SCHEMA_REJECTED' +# Set far enough in the past that founding date never becomes the binding floor +OLD_FOUNDING_DATE = datetime.utcnow().replace(year=datetime.utcnow().year - 10) + @pytest.mark.parametrize( 'test_name, expected_code, message', [ ('INVALID_YEAR', HTTPStatus.UNPROCESSABLE_ENTITY, SCHEMA_REJECTED), - ('FAIL_YEAR-3', HTTPStatus.BAD_REQUEST, 'AGM year must be between -2 or +1 year from current year.'), - ('FAIL_YEAR+2', HTTPStatus.BAD_REQUEST, 'AGM year must be between -2 or +1 year from current year.'), + ('FAIL_YEAR-3', HTTPStatus.BAD_REQUEST, None), + ('FAIL_YEAR+2', HTTPStatus.BAD_REQUEST, None), ('SUCCESS-2', None, None), ('SUCCESS+1', None, None), ('SUCCESS', None, None) ] ) def test_validate_agm_year(session, mocker, test_name, expected_code, message, monkeypatch): - """Assert validate agm year.""" + """Assert validate agm year for an established business (founding date well outside the lookback window).""" monkeypatch.setattr( 'legal_api.services.flags.value', lambda flag, default=None: "BC BEN CC ULC C CBEN CCC CUL" if flag == 'supported-agm-location-change-entities' else default ) - business = factory_business(identifier='BC1234567', entity_type='BC', founding_date=datetime.utcnow()) + business = factory_business(identifier='BC1234567', entity_type='BC', founding_date=OLD_FOUNDING_DATE) filing = copy.deepcopy(FILING_HEADER) filing['filing']['agmLocationChange'] = copy.deepcopy(AGM_LOCATION_CHANGE) filing['filing']['header']['name'] = 'agmLocationChange' + current_year = LegislationDatetime.now().year + expected_min = current_year - 2 + expected_max = current_year + 1 + if test_name == 'INVALID_YEAR': filing['filing']['agmLocationChange']['year'] = 'invalid' elif test_name == 'FAIL_YEAR-3': - filing['filing']['agmLocationChange']['year'] = str(LegislationDatetime.now().year - 3) + filing['filing']['agmLocationChange']['year'] = str(current_year - 3) elif test_name == 'FAIL_YEAR+2': - filing['filing']['agmLocationChange']['year'] = str(LegislationDatetime.now().year + 2) + filing['filing']['agmLocationChange']['year'] = str(current_year + 2) elif test_name == 'SUCCESS-2': - filing['filing']['agmLocationChange']['year'] = str(LegislationDatetime.now().year - 2) + filing['filing']['agmLocationChange']['year'] = str(current_year - 2) elif test_name == 'SUCCESS+1': - filing['filing']['agmLocationChange']['year'] = str(LegislationDatetime.now().year + 1) + filing['filing']['agmLocationChange']['year'] = str(current_year + 1) elif test_name == 'SUCCESS': - filing['filing']['agmLocationChange']['year'] = str(LegislationDatetime.now().year) + filing['filing']['agmLocationChange']['year'] = str(current_year) err = validate(business, filing) # validate outcomes @@ -71,11 +78,54 @@ def test_validate_agm_year(session, mocker, test_name, expected_code, message, m assert err.code == HTTPStatus.UNPROCESSABLE_ENTITY elif not test_name.startswith('SUCCESS'): assert expected_code == err.code - if message: - assert message == err.msg[0]['error'] + assert f'AGM year must be between {expected_min} and {expected_max}.' == err.msg[0]['error'] + else: + assert not err + + +@pytest.mark.parametrize( + 'test_name, founding_years_ago, year_offset_from_now', + [ + # business founded less than 2 years ago: founding year should raise the floor + # above the default lookback year + ('FAIL_BEFORE_FOUNDING_YEAR', 1, -2), + ('SUCCESS_AT_FOUNDING_YEAR', 1, -1), + # business founded this year: floor should equal the current year + ('FAIL_BEFORE_FOUNDING_THIS_YEAR', 0, -1), + ('SUCCESS_FOUNDING_THIS_YEAR', 0, 0), + ] +) +def test_validate_agm_year_founding_date_floor( + session, mocker, test_name, founding_years_ago, year_offset_from_now, monkeypatch +): + """Assert AGM year cannot be set to a year before the business's founding year.""" + monkeypatch.setattr( + 'legal_api.services.flags.value', + lambda flag, default=None: "BC BEN CC ULC C CBEN CCC CUL" if flag == 'supported-agm-location-change-entities' else default + ) + current_year = LegislationDatetime.now().year + founding_date = datetime.utcnow().replace(year=current_year - founding_years_ago) + business = factory_business(identifier='BC1234567', entity_type='BC', founding_date=founding_date) + + filing = copy.deepcopy(FILING_HEADER) + filing['filing']['agmLocationChange'] = copy.deepcopy(AGM_LOCATION_CHANGE) + filing['filing']['header']['name'] = 'agmLocationChange' + filing['filing']['agmLocationChange']['year'] = str(current_year + year_offset_from_now) + + err = validate(business, filing) + + founding_year = current_year - founding_years_ago + expected_min = max(current_year - 2, founding_year) + expected_max = current_year + 1 + + if test_name.startswith('FAIL'): + assert err is not None + assert err.code == HTTPStatus.BAD_REQUEST + assert f'AGM year must be between {expected_min} and {expected_max}.' == err.msg[0]['error'] else: assert not err + @pytest.mark.parametrize( 'test_name, reason, expected_code, message', [ @@ -91,7 +141,7 @@ def test_validate_agm_reason(session, mocker, test_name, reason, expected_code, 'legal_api.services.flags.value', lambda flag, default=None: "BC BEN CC ULC C CBEN CCC CUL" if flag == 'supported-agm-location-change-entities' else default ) - business = factory_business(identifier='BC1234567', entity_type='BC', founding_date=datetime.utcnow()) + business = factory_business(identifier='BC1234567', entity_type='BC', founding_date=OLD_FOUNDING_DATE) filing = copy.deepcopy(FILING_HEADER) filing['filing']['agmLocationChange'] = copy.deepcopy(AGM_LOCATION_CHANGE) filing['filing']['header']['name'] = 'agmLocationChange'