"""Tests for the CultureForge and high-level Generator.""" from pathlib import Path import pytest from storied.names.forge import CultureForge, ForgedCulture from storied.names.generator import Generator @pytest.fixture def world_dir(tmp_path: Path) -> Path: """A fresh world directory the forge can write to.""" return tmp_path / "test-world" class TestCultureForge: def test_forge_writes_yaml_file(self, world_dir: Path): forge = CultureForge(world_path=world_dir) culture = forge.forge(seed=42) path = world_dir / "cultures" / f"{culture.name}.yaml" assert path.exists() def test_forge_returns_self_named_culture(self, world_dir: Path): forge = CultureForge(world_path=world_dir) culture = forge.forge(seed=42) assert culture.name assert culture.name.isalpha() assert culture.name == culture.name.lower() def test_forge_self_name_in_length_window(self, world_dir: Path): forge = CultureForge(world_path=world_dir) for seed in range(10): culture = forge.forge(seed=seed * 100) assert 4 <= len(culture.name) <= 9, ( f"Culture name {culture.name!r} out of range" ) def test_forge_is_deterministic(self, tmp_path: Path): forge1 = CultureForge(world_path=tmp_path / "a") forge2 = CultureForge(world_path=tmp_path / "b") c1 = forge1.forge(seed=12345) c2 = forge2.forge(seed=12345) assert c1.name == c2.name def test_forge_different_seeds_yield_different_cultures( self, world_dir: Path, ): forge = CultureForge(world_path=world_dir) cultures = [forge.forge(seed=s) for s in range(10)] names = {c.name for c in cultures} assert len(names) >= 9 # tolerate one rare collision def test_forge_with_feel_picks_matching_inventory(self, world_dir: Path): # Forge several coastal cultures and check they came from # inventories tagged "coastal" in the data file. coastal_inventories = { "welsh", "old-norse", "polynesian", "austronesian", "iberian", "japonic", "arabic-port", "yoruboid", "hellenic", "cornish", } for seed in range(5): forge2 = CultureForge(world_path=world_dir.parent / f"w{seed}") culture = forge2.forge(feel="coastal", seed=seed) assert culture.source_inventory in coastal_inventories assert culture.feel == "coastal" def test_forge_collision_handling(self, world_dir: Path): forge = CultureForge(world_path=world_dir) c1 = forge.forge(seed=99) # Forge again with the same seed — must produce a unique name c2 = forge.forge(seed=99) assert c1.name != c2.name def test_load_round_trips(self, world_dir: Path): forge = CultureForge(world_path=world_dir) original = forge.forge(seed=42) loaded = forge.load(original.name) assert loaded is not None assert loaded.name == original.name assert loaded.source_inventory == original.source_inventory assert loaded.templates == original.templates assert loaded.forbidden_clusters == original.forbidden_clusters assert loaded.morphology.applies == original.morphology.applies def test_load_missing_returns_none(self, world_dir: Path): forge = CultureForge(world_path=world_dir) assert forge.load("nonexistent") is None def test_list_names_empty(self, world_dir: Path): forge = CultureForge(world_path=world_dir) assert forge.list_names() == [] def test_list_names_after_forge(self, world_dir: Path): forge = CultureForge(world_path=world_dir) names = [forge.forge(seed=s).name for s in range(3)] assert sorted(names) == forge.list_names() class TestGenerator: @pytest.fixture def populated_world(self, tmp_path: Path) -> Path: world = tmp_path / "world" forge = CultureForge(world_path=world) forge.forge(seed=42, feel="coastal") forge.forge(seed=99, feel="highborn") return world def test_sample_returns_names(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() assert cultures names = gen.sample(culture=cultures[0].name, count=5) assert len(names) == 5 assert all(isinstance(n, str) for n in names) def test_sample_distinct(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() names = gen.sample(culture=cultures[0].name, count=10) assert len(set(names)) == len(names) def test_sample_capitalized(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() for name in gen.sample(culture=cultures[0].name, count=5): assert name[0].isupper() def test_sample_unknown_culture_returns_empty(self, tmp_path: Path): gen = Generator(world_path=tmp_path) assert gen.sample(culture="ghost", count=5) == [] def test_place_kind(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() places = gen.sample( culture=cultures[0].name, count=3, kind="place", ) assert len(places) == 3 def test_list_cultures_returns_loaded(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() assert len(cultures) == 2 for c in cultures: assert isinstance(c, ForgedCulture) def test_rarity_uncommon(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() names = gen.sample( culture=cultures[0].name, count=3, rarity="uncommon", ) assert len(names) == 3 def test_rarity_archaic(self, populated_world: Path): gen = Generator(world_path=populated_world) cultures = gen.list_cultures() names = gen.sample( culture=cultures[0].name, count=3, rarity="archaic", ) assert len(names) == 3