"""Unit tests for Ghost membership helpers.""" from __future__ import annotations from datetime import datetime import pytest from account.services.ghost_membership import ( _iso, _to_str_or_none, _member_email, _price_cents, _sanitize_member_payload, ) class TestIso: def test_none(self): assert _iso(None) is None def test_empty(self): assert _iso("") is None def test_z_suffix(self): result = _iso("2024-06-15T12:00:00Z") assert isinstance(result, datetime) assert result.year == 2024 def test_offset(self): result = _iso("2024-06-15T12:00:00+00:00") assert isinstance(result, datetime) class TestToStrOrNone: def test_none(self): assert _to_str_or_none(None) is None def test_dict(self): assert _to_str_or_none({"a": 1}) is None def test_list(self): assert _to_str_or_none([1, 2]) is None def test_bytes(self): assert _to_str_or_none(b"hello") is None def test_empty_string(self): assert _to_str_or_none("") is None def test_whitespace_only(self): assert _to_str_or_none(" ") is None def test_valid_string(self): assert _to_str_or_none("hello") == "hello" def test_int(self): assert _to_str_or_none(42) == "42" def test_strips_whitespace(self): assert _to_str_or_none(" hi ") == "hi" def test_set(self): assert _to_str_or_none({1, 2}) is None def test_tuple(self): assert _to_str_or_none((1,)) is None def test_bytearray(self): assert _to_str_or_none(bytearray(b"x")) is None class TestMemberEmail: def test_normal(self): assert _member_email({"email": "USER@EXAMPLE.COM"}) == "user@example.com" def test_none(self): assert _member_email({"email": None}) is None def test_empty(self): assert _member_email({"email": ""}) is None def test_whitespace(self): assert _member_email({"email": " "}) is None def test_missing_key(self): assert _member_email({}) is None def test_strips(self): assert _member_email({"email": " a@b.com "}) == "a@b.com" class TestPriceCents: def test_valid(self): assert _price_cents({"price": {"amount": 1500}}) == 1500 def test_string_amount(self): assert _price_cents({"price": {"amount": "2000"}}) == 2000 def test_missing_price(self): assert _price_cents({}) is None def test_missing_amount(self): assert _price_cents({"price": {}}) is None def test_none_amount(self): assert _price_cents({"price": {"amount": None}}) is None def test_nested_none(self): assert _price_cents({"price": None}) is None class TestSanitizeMemberPayload: def test_email_lowercased(self): result = _sanitize_member_payload({"email": "USER@EXAMPLE.COM"}) assert result["email"] == "user@example.com" def test_empty_email_excluded(self): result = _sanitize_member_payload({"email": ""}) assert "email" not in result def test_name_included(self): result = _sanitize_member_payload({"name": "Alice"}) assert result["name"] == "Alice" def test_note_included(self): result = _sanitize_member_payload({"note": "VIP"}) assert result["note"] == "VIP" def test_subscribed_bool(self): result = _sanitize_member_payload({"subscribed": 1}) assert result["subscribed"] is True def test_labels_with_id(self): result = _sanitize_member_payload({ "labels": [{"id": "abc"}, {"name": "VIP"}] }) assert result["labels"] == [{"id": "abc"}, {"name": "VIP"}] def test_labels_empty_items_excluded(self): result = _sanitize_member_payload({ "labels": [{"id": None, "name": None}] }) assert "labels" not in result def test_newsletters_with_id(self): result = _sanitize_member_payload({ "newsletters": [{"id": "n1", "subscribed": True}] }) assert result["newsletters"] == [{"subscribed": True, "id": "n1"}] def test_newsletters_default_subscribed(self): result = _sanitize_member_payload({ "newsletters": [{"name": "Weekly"}] }) assert result["newsletters"][0]["subscribed"] is True def test_dict_email_excluded(self): result = _sanitize_member_payload({"email": {"bad": "input"}}) assert "email" not in result def test_id_passthrough(self): result = _sanitize_member_payload({"id": "ghost-member-123"}) assert result["id"] == "ghost-member-123" def test_empty_payload(self): result = _sanitize_member_payload({}) assert result == {}