Files
tarot/tests/test_attributes.py

620 lines
22 KiB
Python
Raw Normal View History

2025-11-25 22:19:36 -08:00
"""Comprehensive tests for Tarot attributes, alphabets, ciphers, and numerology."""
from datetime import datetime
import pytest
from src.tarot.attributes import (
Month, Day, Weekday, Hour, ClockHour, Zodiac, Suit, Meaning, Letter, Sephera, Degree, Element,
AstrologicalInfluence, TreeOfLife, Correspondences, CardImage,
EnglishAlphabet, GreekAlphabet, HebrewAlphabet, Number, Color, Planet, God,
Cipher, CipherResult,
)
from src.tarot.card.data import CardDataLoader, calculate_digital_root
# ============================================================================
# Basic Attribute Tests
# ============================================================================
class TestMonth:
def test_month_creation(self):
month = Month(1, "January", "Capricorn", "Aquarius")
assert month.number == 1
assert month.name == "January"
assert month.zodiac_start == "Capricorn"
def test_month_all_months(self):
months = [
Month(i, f"Month_{i}", "Sign_1", "Sign_2")
for i in range(1, 13)
]
assert len(months) == 12
assert months[0].number == 1
assert months[11].number == 12
class TestDay:
def test_day_creation(self):
day = Day(1, "Sunday", "Sun")
assert day.number == 1
assert day.name == "Sunday"
assert day.planetary_correspondence == "Sun"
def test_all_weekdays(self):
days = [
Day(i, f"Day_{i}", f"Planet_{i}")
for i in range(1, 8)
]
assert len(days) == 7
class TestWeekday:
def test_weekday_creation(self):
weekday = Weekday(1, "Monday", "Moon")
assert weekday.name == "Monday"
assert weekday.planetary_correspondence == "Moon"
assert weekday.is_weekend is False
def test_weekday_invalid_number(self):
with pytest.raises(ValueError):
Weekday(0, "Zero", "Sun")
class TestClockHour:
def test_clock_hour_creation(self):
hour = ClockHour(13, 1, "PM", "Mars", "Afternoon surge")
assert hour.hour_24 == 13
assert hour.hour_12 == 1
assert hour.time_of_day == "Night"
def test_clock_hour_invalid_period(self):
with pytest.raises(ValueError):
ClockHour(12, 12, "XX", "Sun")
class TestZodiac:
def test_zodiac_creation(self):
zodiac = Zodiac("Aries", "", "Fire", "Mars", "Mar 21 - Apr 19")
assert zodiac.name == "Aries"
assert zodiac.element == "Fire"
assert zodiac.ruling_planet == "Mars"
class TestSuit:
def test_suit_creation(self):
suit = Suit("Cups", "Water", "Chalice", 1)
assert suit.name == "Cups"
assert suit.element == "Water"
assert suit.number == 1
class TestMeaning:
def test_meaning_creation(self):
meaning = Meaning("This is positive", "This is negative")
assert meaning.upright == "This is positive"
assert meaning.reversed == "This is negative"
# ============================================================================
# Sepheric Tests
# ============================================================================
class TestSephera:
def test_sephera_creation(self):
sephera = Sephera(1, "Kether", "כתר", "Crown", "Metatron", "Chaioth", "Primum")
assert sephera.number == 1
assert sephera.name == "Kether"
assert sephera.hebrew_name == "כתר"
def test_all_sephera(self):
sephera_list = [
Sephera(i, f"Sephera_{i}", "Hebrew", "Meaning", "Angel", "Order", "Chakra")
for i in range(1, 11)
]
assert len(sephera_list) == 10
# ============================================================================
# Alphabet Tests
# ============================================================================
class TestEnglishAlphabet:
def test_english_letter_creation(self):
letter = EnglishAlphabet("A", 1, "ay")
assert letter.letter == "A"
assert letter.position == 1
assert letter.sound == "ay"
def test_english_invalid_position_low(self):
with pytest.raises(ValueError):
EnglishAlphabet("A", 0, "ay")
def test_english_invalid_position_high(self):
with pytest.raises(ValueError):
EnglishAlphabet("A", 27, "ay")
def test_english_invalid_letter(self):
with pytest.raises(ValueError):
EnglishAlphabet("AB", 1, "ay")
def test_english_all_letters(self):
letters = [EnglishAlphabet(chr(65 + i), i + 1, "sound") for i in range(26)]
assert len(letters) == 26
assert letters[0].letter == "A"
assert letters[25].letter == "Z"
class TestGreekAlphabet:
def test_greek_letter_creation(self):
letter = GreekAlphabet("Α", 1, "alpha")
assert letter.letter == "Α"
assert letter.position == 1
assert letter.transliteration == "alpha"
def test_greek_invalid_position_low(self):
with pytest.raises(ValueError):
GreekAlphabet("Α", 0, "alpha")
def test_greek_invalid_position_high(self):
with pytest.raises(ValueError):
GreekAlphabet("Α", 25, "alpha")
def test_greek_all_letters(self):
letters = [GreekAlphabet(f"Α{i}", i + 1, f"greek_{i}") for i in range(24)]
assert len(letters) == 24
class TestHebrewAlphabet:
def test_hebrew_letter_creation(self):
letter = HebrewAlphabet("א", 1, "aleph", "Start")
assert letter.letter == "א"
assert letter.position == 1
assert letter.transliteration == "aleph"
assert letter.meaning == "Start"
def test_hebrew_invalid_position_low(self):
with pytest.raises(ValueError):
HebrewAlphabet("א", 0, "aleph", "Start")
def test_hebrew_invalid_position_high(self):
with pytest.raises(ValueError):
HebrewAlphabet("א", 23, "aleph", "Start")
def test_hebrew_all_letters(self):
letters = [HebrewAlphabet(f"א{i}", i + 1, f"hebrew_{i}", f"meaning_{i}") for i in range(22)]
assert len(letters) == 22
# ============================================================================
# Number Tests
# ============================================================================
class TestNumber:
def test_number_creation(self):
num = Number(1, "Kether", "Spirit", 0) # compliment is auto-calculated
assert num.value == 1
assert num.sephera == "Kether"
assert num.compliment == 8 # 10 - 1 = 9, but 9->9, so 1->8
def test_number_compliments(self):
"""Test that compliments are auto-calculated correctly."""
test_cases = [(1, 8), (2, 7), (3, 6), (4, 5), (5, 4), (6, 3), (7, 2), (8, 1), (9, 9)]
for value, expected_compliment in test_cases:
num = Number(value, f"Sephera_{value}", "Element", 0)
assert num.compliment == expected_compliment
def test_number_invalid_value_low(self):
with pytest.raises(ValueError):
Number(0, "Sephera", "Element", 0)
def test_number_invalid_value_high(self):
with pytest.raises(ValueError):
Number(10, "Sephera", "Element", 0)
def test_all_numbers(self):
numbers = [Number(i, f"Sephera_{i}", "Element", 0) for i in range(1, 10)]
assert len(numbers) == 9
# ============================================================================
# Color Tests
# ============================================================================
class TestColor:
def test_color_creation(self):
color = Color("Red", "#FF0000", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
assert color.name == "Red"
assert color.hex_value == "#FF0000"
assert color.rgb == (255, 0, 0)
assert color.number == 5
def test_color_invalid_hex_format(self):
with pytest.raises(ValueError):
Color("Red", "FF0000", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
def test_color_invalid_hex_length(self):
with pytest.raises(ValueError):
Color("Red", "#FF00", (255, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
def test_color_invalid_rgb_low(self):
with pytest.raises(ValueError):
Color("Red", "#FF0000", (-1, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
def test_color_invalid_rgb_high(self):
with pytest.raises(ValueError):
Color("Red", "#FF0000", (256, 0, 0), "Gevurah", 5, "Fire", "Briah", "Power")
def test_color_all_valid_rgb(self):
for r in [0, 128, 255]:
for g in [0, 128, 255]:
for b in [0, 128, 255]:
color = Color("Test", "#000000", (r, g, b), "Sephera", 1, "Element", "Scale", "Meaning")
assert color.rgb == (r, g, b)
# ============================================================================
# Planet Tests
# ============================================================================
class TestPlanet:
def test_planet_creation(self):
number = Number(6, "Tiphareth", "Fire", 0)
color = Color("Gold", "#FFD700", (255, 215, 0), "Tiphareth", 6, "Fire", "Yetzirah", "Beauty")
planet = Planet(
name="Sun",
symbol="",
element="Fire",
ruling_zodiac=["Leo"],
associated_numbers=[number],
associated_letters=["Resh"],
keywords=["Vitality"],
color=color,
description="Center",
)
assert planet.name == "Sun"
assert planet.color is color
assert planet.associated_numbers[0].value == 6
class TestGod:
def test_god_creation(self):
number = Number(6, "Tiphareth", "Fire", 0)
planet = Planet(
name="Sun",
symbol="",
element="Fire",
ruling_zodiac=["Leo"],
associated_numbers=[number],
associated_letters=["Resh"],
keywords=["Vitality"],
color=None,
description="Center",
)
god = God(
name="Ra",
culture="Egyptian",
pantheon="Solar",
domains=["Consciousness"],
epithets=["Sun God"],
mythology="Solar deity",
sephera_numbers=[6],
path_numbers=[15],
planets=["Sun"],
elements=["Fire"],
zodiac_signs=["Leo"],
associated_planet=planet,
associated_element=None,
associated_numbers=[number],
tarot_trumps=["XIX - The Sun"],
keywords=["Vitality"],
description="Solar current",
)
assert god.culture_key() == "egyptian"
assert god.primary_number() is number
assert 6 in god.sephera_numbers
def test_god_without_planet(self):
god = God(
name="Hecate",
culture="Greek",
pantheon="Chthonic",
domains=["Magic"],
epithets=["Triple Goddess"],
mythology="Guardian of crossroads",
sephera_numbers=[],
path_numbers=[29],
planets=[],
elements=["Water"],
zodiac_signs=["Pisces"],
associated_planet=None,
associated_element=None,
associated_numbers=[],
tarot_trumps=["XVIII - The Moon"],
keywords=["Mystery"],
description="Night guide",
)
assert god.primary_number() is None
assert "water" in [elem.lower() for elem in god.elements]
# ============================================================================
# Cipher Tests
# ============================================================================
class TestCipher:
def test_cipher_mapping_basic(self):
cipher = Cipher("Test", "test", [1, 2, 3])
mapping = cipher.mapping_for_alphabet(["A", "B", "C"])
assert mapping == {"A": 1, "B": 2, "C": 3}
def test_cipher_cycle_expansion(self):
cipher = Cipher("Cycle", "cycle", [1, 2], cycle=True)
mapping = cipher.mapping_for_alphabet(["A", "B", "C", "D"])
assert mapping["C"] == 1
assert mapping["D"] == 2
def test_cipher_subset(self):
cipher = Cipher("Subset", "subset", [5, 6], letter_subset={"A", "C"})
mapping = cipher.mapping_for_alphabet(["A", "B", "C"])
assert "B" not in mapping
assert mapping["C"] == 6
class TestCipherResult:
def test_cipher_result_totals(self):
cipher = Cipher("Test", "test", [1, 2, 3])
result = CipherResult("abc", cipher, "english", (1, 2, 3))
assert result.total == 6
assert result.as_string("-") == "1-2-3"
assert str(result) == "123"
# ============================================================================
# Digital Root Tests
# ============================================================================
class TestDigitalRoot:
def test_digital_root_single_digit(self):
"""Single digits should return themselves."""
for i in range(1, 10):
assert calculate_digital_root(i) == i
def test_digital_root_two_digit(self):
"""Test two-digit numbers."""
assert calculate_digital_root(10) == 1 # 1+0 = 1
assert calculate_digital_root(14) == 5 # 1+4 = 5
assert calculate_digital_root(19) == 1 # 1+9 = 10, 1+0 = 1
assert calculate_digital_root(18) == 9 # 1+8 = 9
def test_digital_root_large_numbers(self):
"""Test large numbers."""
assert calculate_digital_root(99) == 9 # 9+9 = 18, 1+8 = 9
assert calculate_digital_root(100) == 1 # 1+0+0 = 1
assert calculate_digital_root(123) == 6 # 1+2+3 = 6
def test_digital_root_tarot_cards(self):
"""Test digital root for Tarot card numbers."""
# Major Arcana cards 0-21
assert calculate_digital_root(14) == 5 # Card 14 (Temperance) -> 5
assert calculate_digital_root(21) == 3 # Card 21 (The World) -> 3
assert calculate_digital_root(1) == 1 # Card 1 (Magician) -> 1
def test_digital_root_invalid_input(self):
"""Test that invalid inputs raise errors."""
with pytest.raises(ValueError):
calculate_digital_root(0)
with pytest.raises(ValueError):
calculate_digital_root(-5)
# ============================================================================
# CardDataLoader Tests
# ============================================================================
class TestCardDataLoader:
@pytest.fixture
def loader(self):
return CardDataLoader()
def test_loader_initialization(self, loader):
"""Test that loader initializes correctly."""
assert loader is not None
def test_load_numbers(self, loader):
"""Test loading numbers."""
for i in range(1, 10):
num = loader.number(i)
assert num is not None
assert num.value == i
assert num.compliment == (9 - i if i != 9 else 9)
def test_load_colors(self, loader):
"""Test loading colors."""
for i in range(1, 11):
color = loader.color(i)
assert color is not None
assert color.number == i
def test_load_sephera(self, loader):
"""Test loading individual Sephiroth."""
for i in range(1, 11):
sephera = loader.sephera(i)
assert sephera is not None
assert sephera.number == i
def test_load_ciphers(self, loader):
"""Ensure cipher catalog is populated."""
ciphers = loader.cipher()
assert "english_simple" in ciphers
assert ciphers["english_simple"].default_alphabet == "english"
def test_word_cipher_request(self, loader):
"""word().cipher() should return meaningful totals."""
result = loader.word("tarot").cipher("english_simple")
assert isinstance(result, CipherResult)
assert result.total == 74
assert result.alphabet_name == "english"
def test_word_cipher_custom_alphabet(self, loader):
result = loader.word("אמש").cipher("kabbalah_three_mother")
assert result.values == (1, 40, 300)
def test_trigram_line_diagram(self, loader):
from letter import trigram
tri = trigram.trigram.name("Zhen") # Thunder
assert tri is not None
assert tri.data.line_diagram == "|::"
def test_hexagram_line_diagram(self, loader):
from letter import hexagram
hex_result = hexagram.hexagram.filter('number:1').first()
assert hex_result is not None
assert hex_result.data.line_diagram == "||||||"
def test_color_by_number(self, loader):
"""Test getting color by number with digital root."""
# 14 -> digital root 5 -> Gevurah -> Red Scarlet
color = loader.color_by_number(14)
assert color is not None
assert color.number == 5
assert "Red" in color.name
def test_number_by_digital_root(self, loader):
"""Test getting number by digital root."""
num = loader.number_by_digital_root(14)
assert num is not None
assert num.value == 5
def test_digital_root_method(self, loader):
"""Test the digital root method on loader."""
assert loader.digital_root(14) == 5
assert loader.digital_root(99) == 9
assert loader.digital_root(5) == 5
def test_cipher_lookup(self, loader):
"""Test loading individual cipher definitions."""
systems = ["hebrew_standard", "english_simple", "greek_isopsephy", "reduction"]
for system_name in systems:
cipher = loader.cipher(system_name)
assert cipher is not None
assert cipher.key == system_name
def test_planet_lookup(self, loader):
"""Test loading planetary correspondences."""
planets = loader.planet()
assert "mercury" in planets
mercury = loader.planet("Mercury")
assert mercury is not None
assert mercury.associated_letters[0] == "Beth"
assert mercury.associated_numbers and mercury.associated_numbers[0].value == 8
def test_weekday_lookup(self, loader):
weekdays = loader.weekday()
assert "monday" in weekdays
sunday = loader.weekday("Sunday")
assert sunday is not None
assert sunday.is_weekend is True
assert sunday.planetary_correspondence == "Sun"
def test_clock_hour_lookup(self, loader):
hours = loader.clock_hour()
assert len(hours) == 24
midnight = loader.clock_hour(0)
assert midnight is not None
assert midnight.period == "AM"
assert midnight.time_of_day == "Day"
afternoon = loader.clock_hour(15)
assert afternoon is not None
assert afternoon.period == "PM"
assert afternoon.time_of_day == "Night"
def test_god_registry(self, loader):
gods = loader.god()
assert "apollo" in gods
assert "ra" in gods
apollo = gods["apollo"]
assert apollo.culture == "Greek"
assert apollo.associated_planet is not None
assert apollo.associated_planet.name == "Sun"
def test_gods_by_culture(self, loader):
greek_gods = loader.gods_by_culture("Greek")
assert greek_gods
assert all(god.culture == "Greek" for god in greek_gods.values())
assert "apollo" in greek_gods
assert "ra" not in greek_gods
def test_god_with_culture_filter(self, loader):
apollo = loader.god("Apollo", culture="Greek")
assert apollo is not None
assert apollo.culture == "Greek"
assert apollo.associated_planet is not None
assert loader.god("Apollo", culture="Roman") is None
def test_temporal_correspondence(self, loader):
moment = datetime(2024, 3, 21, 15, 30)
snapshot = loader.temporal_correspondence(moment)
assert snapshot.timestamp == moment
assert snapshot.weekday is not None
assert snapshot.weekday.name == "Thursday"
assert snapshot.clock_hour is not None
assert snapshot.clock_hour.hour_24 == 15
assert snapshot.planet is not None
assert snapshot.planet.name == snapshot.clock_hour.planetary_ruler
if snapshot.number is not None:
assert snapshot.color is snapshot.number.color
assert snapshot.card.name # deterministic card returned
assert snapshot.hexagram is not None
def test_alphabet_catalog(self, loader):
"""Test loading alphabets."""
alphabets = loader.alphabet()
assert "english" in alphabets
assert "greek" in alphabets
assert "hebrew" in alphabets
assert len(alphabets["english"]) == 26
assert len(alphabets["greek"]) == 24
assert len(alphabets["hebrew"]) == 22
def test_sephera_loaded(self, loader):
"""Test that all 10 Sephiroth are loaded."""
sephera_count = 0
for i in range(1, 11):
color = loader.color(i)
if color is not None and color.sephera:
sephera_count += 1
assert sephera_count == 10
class TestDigitalRootIntegration:
"""Integration tests for digital root with Tarot cards."""
def test_all_major_arcana_digital_roots(self):
"""Test digital root for all Major Arcana cards (0-21)."""
loader = CardDataLoader()
# All Major Arcana cards should map to colors 1-9
for card_num in range(22):
if card_num == 0:
continue # Skip The Fool (0)
color = loader.color_by_number(card_num)
assert color is not None
assert 1 <= color.number <= 9
def test_color_consistency(self):
"""Test that equivalent numbers map to same color."""
loader = CardDataLoader()
# 5 and 14 should map to same color (both have digital root 5)
color_5 = loader.color_by_number(5)
color_14 = loader.color_by_number(14)
assert color_5 is not None
assert color_14 is not None
assert color_5.number == color_14.number
assert color_5.hex_value == color_14.hex_value