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,169 @@
|
||||
"""Property-based tests for the GitLab Rotator module using Hypothesis."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from hypothesis import given, settings, assume
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from scripts.pat_manager.models import PatEntry, RotationResult
|
||||
from scripts.pat_manager.registry import PatRegistry
|
||||
|
||||
|
||||
# --- Strategies ---
|
||||
|
||||
_SERVICES_AND_METHODS = [
|
||||
("GitLab", "auto"),
|
||||
("Jira", "manual"),
|
||||
("Confluence", "manual"),
|
||||
("OrgMyLife", "manual"),
|
||||
]
|
||||
|
||||
|
||||
@st.composite
|
||||
def valid_iso_dates(draw: st.DrawFn) -> str:
|
||||
"""Generate valid ISO 8601 date strings (YYYY-MM-DD)."""
|
||||
year = draw(st.integers(min_value=2020, max_value=2099))
|
||||
month = draw(st.integers(min_value=1, max_value=12))
|
||||
if month in (1, 3, 5, 7, 8, 10, 12):
|
||||
max_day = 31
|
||||
elif month in (4, 6, 9, 11):
|
||||
max_day = 30
|
||||
elif month == 2:
|
||||
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
|
||||
max_day = 29
|
||||
else:
|
||||
max_day = 28
|
||||
else:
|
||||
max_day = 28
|
||||
day = draw(st.integers(min_value=1, max_value=max_day))
|
||||
return f"{year:04d}-{month:02d}-{day:02d}"
|
||||
|
||||
|
||||
@st.composite
|
||||
def future_iso_dates(draw: st.DrawFn) -> str:
|
||||
"""Generate future ISO 8601 date strings (always in the future)."""
|
||||
year = draw(st.integers(min_value=2026, max_value=2099))
|
||||
month = draw(st.integers(min_value=1, max_value=12))
|
||||
if month in (1, 3, 5, 7, 8, 10, 12):
|
||||
max_day = 31
|
||||
elif month in (4, 6, 9, 11):
|
||||
max_day = 30
|
||||
elif month == 2:
|
||||
if year % 4 == 0 and (year % 100 != 0 or year % 400 == 0):
|
||||
max_day = 29
|
||||
else:
|
||||
max_day = 28
|
||||
else:
|
||||
max_day = 28
|
||||
day = draw(st.integers(min_value=1, max_value=max_day))
|
||||
return f"{year:04d}-{month:02d}-{day:02d}"
|
||||
|
||||
|
||||
@st.composite
|
||||
def valid_token_names(draw: st.DrawFn) -> str:
|
||||
"""Generate valid token names (1-128 chars, printable ASCII)."""
|
||||
name = draw(
|
||||
st.text(
|
||||
alphabet=st.characters(
|
||||
whitelist_categories=("L", "N", "P", "S"),
|
||||
whitelist_characters="-_",
|
||||
),
|
||||
min_size=1,
|
||||
max_size=128,
|
||||
)
|
||||
)
|
||||
assume(len(name.strip()) > 0)
|
||||
return name
|
||||
|
||||
|
||||
@st.composite
|
||||
def gitlab_pat_entries(draw: st.DrawFn) -> PatEntry:
|
||||
"""Generate a valid GitLab PatEntry with a valid expiry_date."""
|
||||
token_name = draw(valid_token_names())
|
||||
expiry_date = draw(valid_iso_dates())
|
||||
return PatEntry(
|
||||
service="GitLab",
|
||||
token_name=token_name,
|
||||
expiry_date=expiry_date,
|
||||
renewal_method="auto",
|
||||
)
|
||||
|
||||
|
||||
@st.composite
|
||||
def successful_rotation_results(draw: st.DrawFn, original_expiry: str) -> RotationResult:
|
||||
"""Generate a successful RotationResult with a new expiry date different from the original."""
|
||||
new_expiry = draw(future_iso_dates())
|
||||
assume(new_expiry != original_expiry)
|
||||
return RotationResult(
|
||||
success=True,
|
||||
new_token="glpat-" + draw(st.text(
|
||||
alphabet=st.characters(whitelist_categories=("L", "N")),
|
||||
min_size=10,
|
||||
max_size=30,
|
||||
)),
|
||||
new_expiry_date=new_expiry,
|
||||
error_message=None,
|
||||
)
|
||||
|
||||
|
||||
# --- Property 6 Test ---
|
||||
|
||||
# Feature: pat-renewal, Property 6: Registry update after successful rotation
|
||||
class TestRegistryUpdateAfterSuccessfulRotation:
|
||||
"""
|
||||
**Validates: Requirements 3.3**
|
||||
|
||||
Property 6: For any GitLab PAT entry and any valid rotation response
|
||||
containing a new expiry date, after successful rotation the registry
|
||||
entry's expiry_date SHALL equal the new expiry date from the rotation
|
||||
response.
|
||||
"""
|
||||
|
||||
@given(
|
||||
entry=gitlab_pat_entries(),
|
||||
new_expiry=future_iso_dates(),
|
||||
)
|
||||
@settings(max_examples=100)
|
||||
def test_registry_entry_updated_with_new_expiry_after_rotation(
|
||||
self, entry: PatEntry, new_expiry: str, tmp_path_factory
|
||||
) -> None:
|
||||
"""After successful rotation, the registry entry's expiry_date equals
|
||||
the new expiry date from the rotation response."""
|
||||
assume(new_expiry != entry.expiry_date)
|
||||
|
||||
tmp_path = tmp_path_factory.mktemp("prop6_rotation")
|
||||
registry = PatRegistry()
|
||||
file_path = tmp_path / "registry.json"
|
||||
|
||||
# Step 1: Save a registry with the original entry
|
||||
registry.save(file_path, [entry])
|
||||
|
||||
# Step 2: Create a RotationResult with success=True and the new expiry_date
|
||||
rotation_result = RotationResult(
|
||||
success=True,
|
||||
new_token="glpat-new-rotated-token",
|
||||
new_expiry_date=new_expiry,
|
||||
error_message=None,
|
||||
)
|
||||
|
||||
# Step 3: Simulate the registry update after successful rotation
|
||||
# Load the registry, update the entry's expiry_date, save it back
|
||||
entries = registry.load(file_path)
|
||||
assert len(entries) == 1
|
||||
|
||||
# Update the entry's expiry_date to the new one from RotationResult
|
||||
entries[0].expiry_date = rotation_result.new_expiry_date
|
||||
registry.save(file_path, entries)
|
||||
|
||||
# Step 4: Reload and verify the entry's expiry_date equals the new_expiry_date
|
||||
reloaded = registry.load(file_path)
|
||||
assert len(reloaded) == 1
|
||||
assert reloaded[0].expiry_date == new_expiry
|
||||
assert reloaded[0].expiry_date == rotation_result.new_expiry_date
|
||||
|
||||
# Also verify other fields remain unchanged
|
||||
assert reloaded[0].service == entry.service
|
||||
assert reloaded[0].token_name == entry.token_name
|
||||
assert reloaded[0].renewal_method == entry.renewal_method
|
||||
Reference in New Issue
Block a user