feat: publish Hermes Nextcloud calendar MCP under AGPL-3.0
This commit is contained in:
102
tests/test_setup_helpers.py
Normal file
102
tests/test_setup_helpers.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import pytest
|
||||
|
||||
from hermes_caldav_mcp import setup_helpers as helpers
|
||||
from hermes_caldav_mcp.types import SafeError
|
||||
|
||||
|
||||
def test_timezone_uses_valid_local_metadata_and_ignores_invalid_hint(tmp_path):
|
||||
zones = tmp_path / "zoneinfo"
|
||||
(zones / "Europe").mkdir(parents=True)
|
||||
(zones / "Europe/Berlin").touch()
|
||||
localtime = tmp_path / "localtime"
|
||||
localtime.symlink_to(zones / "Europe/Berlin")
|
||||
assert (
|
||||
helpers.detect_timezone(
|
||||
{"TZ": "Invalid/City"}, localtime, tmp_path / "missing", zones
|
||||
)
|
||||
== "Europe/Berlin"
|
||||
)
|
||||
assert (
|
||||
helpers.detect_timezone(
|
||||
{"TZ": "America/New_York"}, localtime, tmp_path / "missing", zones
|
||||
)
|
||||
== "America/New_York"
|
||||
)
|
||||
localtime.unlink()
|
||||
metadata = tmp_path / "timezone"
|
||||
metadata.write_text("Europe/Berlin\n")
|
||||
assert helpers.detect_timezone({}, localtime, metadata, zones) == "Europe/Berlin"
|
||||
metadata.write_text("invalid")
|
||||
assert helpers.detect_timezone({}, localtime, metadata, zones) is None
|
||||
|
||||
|
||||
def test_city_picker_recovers_and_needs_no_iana_identifier(monkeypatch):
|
||||
replies = iter(["Nowhere", "berlin", ""])
|
||||
monkeypatch.setattr("builtins.input", lambda _: next(replies))
|
||||
assert helpers.choose_timezone() == "Europe/Berlin"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value,want",
|
||||
[
|
||||
("cloud.example.test/nextcloud/", "https://cloud.example.test/nextcloud"),
|
||||
(
|
||||
"https://cloud.example.test/nextcloud/index.php/apps/calendar/",
|
||||
"https://cloud.example.test/nextcloud",
|
||||
),
|
||||
(
|
||||
"https://cloud.example.test/nextcloud/apps/calendar",
|
||||
"https://cloud.example.test/nextcloud",
|
||||
),
|
||||
(
|
||||
"https://cloud.example.test/nextcloud/index.php/apps/calendar/dayGridMonth/now",
|
||||
"https://cloud.example.test/nextcloud",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_address_accepts_browser_calendar_url_and_adds_https(value, want):
|
||||
assert helpers.normalize_address(value) == want
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"value",
|
||||
[
|
||||
"http://cloud.example.test",
|
||||
"https://name:secret@cloud.example.test",
|
||||
"https://cloud.example.test/#token",
|
||||
"https://cloud.example.test/../other",
|
||||
"",
|
||||
"https://cloud.example.test/remote.php/dav/calendars/user/",
|
||||
],
|
||||
)
|
||||
def test_address_keeps_security_boundaries(value):
|
||||
with pytest.raises(SafeError):
|
||||
helpers.normalize_address(value)
|
||||
|
||||
|
||||
def test_calendar_selection_ranges_retry_without_default_all(monkeypatch):
|
||||
replies = iter(["", "1,1", "4", "1-2,3"])
|
||||
monkeypatch.setattr("builtins.input", lambda _: next(replies))
|
||||
assert helpers.choose_calendars(["a", "b", "c"]) == ("a", "b", "c")
|
||||
|
||||
|
||||
def test_single_calendar_can_be_selected_with_enter(monkeypatch):
|
||||
monkeypatch.setattr("builtins.input", lambda _: "")
|
||||
assert helpers.choose_calendars(["a"]) == ("a",)
|
||||
|
||||
|
||||
def test_display_escapes_terminal_and_bidi_controls():
|
||||
rendered = helpers.display("Calendar\x1b[2J\n\u202ehidden")
|
||||
assert "\x1b" not in rendered and "\n" not in rendered and "\u202e" not in rendered
|
||||
assert "Calendar" in rendered
|
||||
|
||||
|
||||
def test_private_ca_validation_rejects_missing_file(tmp_path):
|
||||
with pytest.raises(SafeError):
|
||||
helpers.validate_ca(str(tmp_path / "missing"))
|
||||
# Real certificate validation, not just filesystem existence.
|
||||
path = tmp_path / "invalid.pem"
|
||||
path.write_text("not a certificate")
|
||||
with pytest.raises(SafeError):
|
||||
helpers.validate_ca(str(path))
|
||||
assert helpers.validate_ca(None) is None
|
||||
Reference in New Issue
Block a user