"""Property-based tests for the PAT Manager expiry checker module using Hypothesis.""" from __future__ import annotations import os from unittest.mock import patch import pytest import respx from httpx import Response from hypothesis import given, settings from hypothesis import strategies as st from scripts.pat_manager.checker import ExpiryChecker from scripts.pat_manager.models import CheckResult, PatEntry, PatStatus # --- Strategies --- # HTTP status codes that are NOT in {200-299, 401, 403} # These should all yield "check failed" classification. non_auth_error_codes = st.one_of( st.just(400), st.just(402), st.integers(min_value=404, max_value=499), st.integers(min_value=500, max_value=599), ) # --- Property 12 Test --- # Feature: pat-renewal, Property 12: Non-auth HTTP errors yield "check failed" class TestNonAuthHttpErrorsYieldCheckFailed: """ **Validates: Requirements 6.6** Property 12: For any HTTP response status code that is not 200-299, 401, or 403, the classification SHALL be "check failed". This includes 4xx errors (other than 401/403), 5xx errors, and timeout conditions. """ @given(status_code=non_auth_error_codes) @settings(max_examples=100, deadline=None) async def test_jira_non_auth_errors_yield_check_failed( self, status_code: int ) -> None: """Jira check with non-auth HTTP errors produces check_failed status.""" entry = PatEntry( service="Jira", token_name="test-jira-token", expiry_date="2025-12-01", renewal_method="manual", ) checker = ExpiryChecker(max_retries=0) with patch.dict(os.environ, {"JIRA_URL": "https://jira.example.com"}): with respx.mock: respx.get("https://jira.example.com/rest/api/2/myself").mock( return_value=Response(status_code) ) result = await checker.check_jira(entry, "fake-token") assert result.status == PatStatus.CHECK_FAILED assert result.error_message is not None assert len(result.error_message) > 0 @given(status_code=non_auth_error_codes) @settings(max_examples=100, deadline=None) async def test_confluence_non_auth_errors_yield_check_failed( self, status_code: int ) -> None: """Confluence check with non-auth HTTP errors produces check_failed status.""" entry = PatEntry( service="Confluence", token_name="test-confluence-token", expiry_date="2025-12-01", renewal_method="manual", ) checker = ExpiryChecker(max_retries=0) with patch.dict(os.environ, {"CONFLUENCE_URL": "https://confluence.example.com"}): with respx.mock: respx.get( "https://confluence.example.com/rest/api/user/current" ).mock(return_value=Response(status_code)) result = await checker.check_confluence(entry, "fake-token") assert result.status == PatStatus.CHECK_FAILED assert result.error_message is not None assert len(result.error_message) > 0 @given(status_code=non_auth_error_codes) @settings(max_examples=100, deadline=None) async def test_orgmylife_non_auth_errors_yield_check_failed( self, status_code: int ) -> None: """OrgMyLife check with non-auth HTTP errors produces check_failed status.""" entry = PatEntry( service="OrgMyLife", token_name="test-orgmylife-token", expiry_date="2025-12-01", renewal_method="manual", ) checker = ExpiryChecker(max_retries=0) with patch.dict(os.environ, {"ORGMYLIFE_URL": "https://orgmylife.example.com"}): with respx.mock: respx.get("https://orgmylife.example.com/api/tasks").mock( return_value=Response(status_code) ) result = await checker.check_orgmylife(entry, "fake-token") assert result.status == PatStatus.CHECK_FAILED assert result.error_message is not None assert len(result.error_message) > 0