Migrate all repos into monorepo context folders
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.
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user