Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
116 lines
4.1 KiB
Python
116 lines
4.1 KiB
Python
"""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
|