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,465 @@
|
||||
"""Unit tests for the PAT Registry module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from scripts.pat_manager.errors import RegistryValidationError
|
||||
from scripts.pat_manager.models import PatEntry
|
||||
from scripts.pat_manager.registry import PatRegistry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def registry() -> PatRegistry:
|
||||
"""Create a fresh PatRegistry instance."""
|
||||
return PatRegistry()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_entry_dict() -> dict:
|
||||
"""A valid PAT entry dictionary."""
|
||||
return {
|
||||
"service": "GitLab",
|
||||
"token_name": "ci-pipeline-token",
|
||||
"expiry_date": "2025-08-15",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def valid_registry_data() -> dict:
|
||||
"""A valid registry JSON structure with multiple entries."""
|
||||
return {
|
||||
"version": "1.0",
|
||||
"tokens": [
|
||||
{
|
||||
"service": "GitLab",
|
||||
"token_name": "ci-pipeline-token",
|
||||
"expiry_date": "2025-08-15",
|
||||
"renewal_method": "auto",
|
||||
},
|
||||
{
|
||||
"service": "Jira",
|
||||
"token_name": "jira-api-access",
|
||||
"expiry_date": "2025-07-20",
|
||||
"renewal_method": "manual",
|
||||
},
|
||||
{
|
||||
"service": "OrgMyLife",
|
||||
"token_name": "orchestrator-api-key",
|
||||
"expiry_date": None,
|
||||
"renewal_method": "manual",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
class TestValidateEntry:
|
||||
"""Tests for PatRegistry.validate_entry()."""
|
||||
|
||||
def test_valid_gitlab_entry(self, registry: PatRegistry, valid_entry_dict: dict) -> None:
|
||||
result = registry.validate_entry(valid_entry_dict)
|
||||
assert isinstance(result, PatEntry)
|
||||
assert result.service == "GitLab"
|
||||
assert result.token_name == "ci-pipeline-token"
|
||||
assert result.expiry_date == "2025-08-15"
|
||||
assert result.renewal_method == "auto"
|
||||
|
||||
def test_valid_jira_entry(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "Jira",
|
||||
"token_name": "jira-token",
|
||||
"expiry_date": "2025-12-31",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert result.service == "Jira"
|
||||
assert result.renewal_method == "manual"
|
||||
|
||||
def test_valid_confluence_entry(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "Confluence",
|
||||
"token_name": "conf-token",
|
||||
"expiry_date": "2025-06-01",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert result.service == "Confluence"
|
||||
|
||||
def test_valid_orgmylife_entry(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "OrgMyLife",
|
||||
"token_name": "oml-key",
|
||||
"expiry_date": None,
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert result.service == "OrgMyLife"
|
||||
assert result.expiry_date is None
|
||||
|
||||
def test_null_expiry_date_accepted(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "Jira",
|
||||
"token_name": "no-expiry",
|
||||
"expiry_date": None,
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert result.expiry_date is None
|
||||
|
||||
def test_missing_expiry_date_field_accepted(self, registry: PatRegistry) -> None:
|
||||
"""expiry_date is optional (defaults to None when missing)."""
|
||||
entry = {
|
||||
"service": "Jira",
|
||||
"token_name": "no-expiry-field",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert result.expiry_date is None
|
||||
|
||||
def test_missing_service_field(self, registry: PatRegistry) -> None:
|
||||
entry = {"token_name": "test", "renewal_method": "manual"}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "service" in exc_info.value.failed_fields
|
||||
|
||||
def test_missing_token_name_field(self, registry: PatRegistry) -> None:
|
||||
entry = {"service": "GitLab", "renewal_method": "auto"}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "token_name" in exc_info.value.failed_fields
|
||||
|
||||
def test_missing_renewal_method_field(self, registry: PatRegistry) -> None:
|
||||
entry = {"service": "GitLab", "token_name": "test"}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "renewal_method" in exc_info.value.failed_fields
|
||||
|
||||
def test_invalid_service_value(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitHub",
|
||||
"token_name": "test",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "service" in exc_info.value.failed_fields
|
||||
|
||||
def test_empty_token_name(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "token_name" in exc_info.value.failed_fields
|
||||
|
||||
def test_token_name_too_long(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "x" * 129,
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "token_name" in exc_info.value.failed_fields
|
||||
|
||||
def test_token_name_max_length_accepted(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "x" * 128,
|
||||
"expiry_date": "2025-01-01",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
result = registry.validate_entry(entry)
|
||||
assert len(result.token_name) == 128
|
||||
|
||||
def test_invalid_expiry_date_format(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "test",
|
||||
"expiry_date": "2025/08/15",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "expiry_date" in exc_info.value.failed_fields
|
||||
|
||||
def test_invalid_expiry_date_values(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "test",
|
||||
"expiry_date": "2025-13-01",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "expiry_date" in exc_info.value.failed_fields
|
||||
|
||||
def test_invalid_expiry_date_feb_30(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "test",
|
||||
"expiry_date": "2025-02-30",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "expiry_date" in exc_info.value.failed_fields
|
||||
|
||||
def test_renewal_method_mismatch_gitlab(self, registry: PatRegistry) -> None:
|
||||
"""GitLab must have renewal_method 'auto'."""
|
||||
entry = {
|
||||
"service": "GitLab",
|
||||
"token_name": "test",
|
||||
"expiry_date": "2025-01-01",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "renewal_method" in exc_info.value.failed_fields
|
||||
|
||||
def test_renewal_method_mismatch_jira(self, registry: PatRegistry) -> None:
|
||||
"""Jira must have renewal_method 'manual'."""
|
||||
entry = {
|
||||
"service": "Jira",
|
||||
"token_name": "test",
|
||||
"expiry_date": "2025-01-01",
|
||||
"renewal_method": "auto",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert "renewal_method" in exc_info.value.failed_fields
|
||||
|
||||
def test_non_dict_entry_raises(self, registry: PatRegistry) -> None:
|
||||
with pytest.raises(RegistryValidationError):
|
||||
registry.validate_entry("not a dict") # type: ignore
|
||||
|
||||
def test_multiple_failed_fields(self, registry: PatRegistry) -> None:
|
||||
entry = {
|
||||
"service": "InvalidService",
|
||||
"token_name": "",
|
||||
"renewal_method": "invalid",
|
||||
}
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.validate_entry(entry)
|
||||
assert len(exc_info.value.failed_fields) >= 2
|
||||
|
||||
|
||||
class TestLoad:
|
||||
"""Tests for PatRegistry.load()."""
|
||||
|
||||
def test_load_valid_registry(
|
||||
self, registry: PatRegistry, tmp_path: Path, valid_registry_data: dict
|
||||
) -> None:
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text(json.dumps(valid_registry_data), encoding="utf-8")
|
||||
|
||||
entries = registry.load(file_path)
|
||||
assert len(entries) == 3
|
||||
assert entries[0].service == "GitLab"
|
||||
assert entries[1].service == "Jira"
|
||||
assert entries[2].service == "OrgMyLife"
|
||||
|
||||
def test_load_empty_registry(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text('{"version": "1.0", "tokens": []}', encoding="utf-8")
|
||||
|
||||
entries = registry.load(file_path)
|
||||
assert entries == []
|
||||
|
||||
def test_load_nonexistent_file(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
file_path = tmp_path / "nonexistent.json"
|
||||
with pytest.raises(FileNotFoundError):
|
||||
registry.load(file_path)
|
||||
|
||||
def test_load_invalid_json(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text("not valid json", encoding="utf-8")
|
||||
with pytest.raises(json.JSONDecodeError):
|
||||
registry.load(file_path)
|
||||
|
||||
def test_load_duplicate_entries_raises(
|
||||
self, registry: PatRegistry, tmp_path: Path
|
||||
) -> None:
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"tokens": [
|
||||
{
|
||||
"service": "GitLab",
|
||||
"token_name": "same-name",
|
||||
"expiry_date": "2025-01-01",
|
||||
"renewal_method": "auto",
|
||||
},
|
||||
{
|
||||
"service": "GitLab",
|
||||
"token_name": "same-name",
|
||||
"expiry_date": "2025-06-01",
|
||||
"renewal_method": "auto",
|
||||
},
|
||||
],
|
||||
}
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text(json.dumps(data), encoding="utf-8")
|
||||
|
||||
with pytest.raises(RegistryValidationError) as exc_info:
|
||||
registry.load(file_path)
|
||||
assert "service" in exc_info.value.failed_fields
|
||||
assert "token_name" in exc_info.value.failed_fields
|
||||
|
||||
def test_load_same_token_name_different_services(
|
||||
self, registry: PatRegistry, tmp_path: Path
|
||||
) -> None:
|
||||
"""Same token_name in different services is allowed."""
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"tokens": [
|
||||
{
|
||||
"service": "GitLab",
|
||||
"token_name": "api-token",
|
||||
"expiry_date": "2025-01-01",
|
||||
"renewal_method": "auto",
|
||||
},
|
||||
{
|
||||
"service": "Jira",
|
||||
"token_name": "api-token",
|
||||
"expiry_date": "2025-06-01",
|
||||
"renewal_method": "manual",
|
||||
},
|
||||
],
|
||||
}
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text(json.dumps(data), encoding="utf-8")
|
||||
|
||||
entries = registry.load(file_path)
|
||||
assert len(entries) == 2
|
||||
|
||||
def test_load_invalid_entry_raises(
|
||||
self, registry: PatRegistry, tmp_path: Path
|
||||
) -> None:
|
||||
data = {
|
||||
"version": "1.0",
|
||||
"tokens": [
|
||||
{
|
||||
"service": "InvalidService",
|
||||
"token_name": "test",
|
||||
"renewal_method": "manual",
|
||||
}
|
||||
],
|
||||
}
|
||||
file_path = tmp_path / "registry.json"
|
||||
file_path.write_text(json.dumps(data), encoding="utf-8")
|
||||
|
||||
with pytest.raises(RegistryValidationError):
|
||||
registry.load(file_path)
|
||||
|
||||
|
||||
class TestSave:
|
||||
"""Tests for PatRegistry.save()."""
|
||||
|
||||
def test_save_entries(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
entries = [
|
||||
PatEntry(
|
||||
service="GitLab",
|
||||
token_name="test-token",
|
||||
expiry_date="2025-08-15",
|
||||
renewal_method="auto",
|
||||
),
|
||||
PatEntry(
|
||||
service="Jira",
|
||||
token_name="jira-token",
|
||||
expiry_date=None,
|
||||
renewal_method="manual",
|
||||
),
|
||||
]
|
||||
file_path = tmp_path / "registry.json"
|
||||
registry.save(file_path, entries)
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data["version"] == "1.0"
|
||||
assert len(data["tokens"]) == 2
|
||||
assert data["tokens"][0]["service"] == "GitLab"
|
||||
assert data["tokens"][1]["expiry_date"] is None
|
||||
|
||||
def test_save_empty_list(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
file_path = tmp_path / "registry.json"
|
||||
registry.save(file_path, [])
|
||||
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data == {"version": "1.0", "tokens": []}
|
||||
|
||||
def test_save_creates_parent_directories(
|
||||
self, registry: PatRegistry, tmp_path: Path
|
||||
) -> None:
|
||||
file_path = tmp_path / "subdir" / "nested" / "registry.json"
|
||||
registry.save(file_path, [])
|
||||
assert file_path.exists()
|
||||
|
||||
|
||||
class TestCreateEmpty:
|
||||
"""Tests for PatRegistry.create_empty()."""
|
||||
|
||||
def test_create_empty_file(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
file_path = tmp_path / "new-registry.json"
|
||||
registry.create_empty(file_path)
|
||||
|
||||
assert file_path.exists()
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
assert data == {"version": "1.0", "tokens": []}
|
||||
|
||||
def test_create_empty_loadable(self, registry: PatRegistry, tmp_path: Path) -> None:
|
||||
"""An empty registry created by create_empty should be loadable."""
|
||||
file_path = tmp_path / "new-registry.json"
|
||||
registry.create_empty(file_path)
|
||||
|
||||
entries = registry.load(file_path)
|
||||
assert entries == []
|
||||
|
||||
|
||||
class TestRoundTrip:
|
||||
"""Tests for save -> load round-trip consistency."""
|
||||
|
||||
def test_round_trip_preserves_entries(
|
||||
self, registry: PatRegistry, tmp_path: Path
|
||||
) -> None:
|
||||
original_entries = [
|
||||
PatEntry(
|
||||
service="GitLab",
|
||||
token_name="ci-token",
|
||||
expiry_date="2025-08-15",
|
||||
renewal_method="auto",
|
||||
),
|
||||
PatEntry(
|
||||
service="Jira",
|
||||
token_name="jira-access",
|
||||
expiry_date="2025-12-31",
|
||||
renewal_method="manual",
|
||||
),
|
||||
PatEntry(
|
||||
service="OrgMyLife",
|
||||
token_name="api-key",
|
||||
expiry_date=None,
|
||||
renewal_method="manual",
|
||||
),
|
||||
]
|
||||
|
||||
file_path = tmp_path / "registry.json"
|
||||
registry.save(file_path, original_entries)
|
||||
loaded_entries = registry.load(file_path)
|
||||
|
||||
assert len(loaded_entries) == len(original_entries)
|
||||
for original, loaded in zip(original_entries, loaded_entries):
|
||||
assert loaded.service == original.service
|
||||
assert loaded.token_name == original.token_name
|
||||
assert loaded.expiry_date == original.expiry_date
|
||||
assert loaded.renewal_method == original.renewal_method
|
||||
Reference in New Issue
Block a user