Files
tarot/tests/test_attributes.py
nose 79d4f1a09e k
2025-11-25 22:19:36 -08:00

620 lines
22 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""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