UNSET sentinel pattern: - Add UNSET sentinel in protocols.py for nullable field updates - Fix inability to clear deck description (UNSET=keep, None=clear) - Fix repository inability to clear validation_errors Starter deck improvements: - Remove unused has_starter_deck from CollectionService - Add deprecation notes to old starter deck methods Validation improvements: - Add energy type validation in deck_validator.py - Add energy type validation in deck schemas - Add VALID_ENERGY_TYPES constant Game loading fix: - Fix get_deck_for_game silently skipping invalid cards - Now raises ValueError with clear error message Tests: - Add TestEnergyTypeValidation test class - Add TestGetDeckForGame test class - Add tests for validate_energy_types utility function Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
609 lines
21 KiB
Python
609 lines
21 KiB
Python
"""Tests for the deck validation functions.
|
|
|
|
These tests verify deck validation logic without database dependencies.
|
|
Card lookup is mocked to isolate the validation logic and enable fast,
|
|
deterministic unit tests.
|
|
|
|
The validation functions enforce Mantimon TCG house rules:
|
|
- 40 cards in main deck
|
|
- 20 energy cards in separate energy deck
|
|
- Max 4 copies of any single card
|
|
- At least 1 Basic Pokemon required
|
|
- Ownership check when owned_cards is provided
|
|
"""
|
|
|
|
import pytest
|
|
|
|
from app.core.config import DeckConfig
|
|
from app.core.enums import CardType, EnergyType, PokemonStage
|
|
from app.core.models.card import CardDefinition
|
|
from app.services.deck_validator import (
|
|
VALID_ENERGY_TYPES,
|
|
ValidationResult,
|
|
count_basic_pokemon,
|
|
validate_cards_exist,
|
|
validate_deck,
|
|
validate_energy_types,
|
|
)
|
|
|
|
# =============================================================================
|
|
# Test Fixtures
|
|
# =============================================================================
|
|
|
|
|
|
def make_basic_pokemon(card_id: str) -> CardDefinition:
|
|
"""Create a Basic Pokemon card definition."""
|
|
return CardDefinition(
|
|
id=card_id,
|
|
name=f"Pokemon {card_id}",
|
|
card_type=CardType.POKEMON,
|
|
hp=60,
|
|
pokemon_type=EnergyType.LIGHTNING,
|
|
stage=PokemonStage.BASIC,
|
|
)
|
|
|
|
|
|
def make_stage1_pokemon(card_id: str) -> CardDefinition:
|
|
"""Create a Stage 1 Pokemon card definition."""
|
|
return CardDefinition(
|
|
id=card_id,
|
|
name=f"Pokemon {card_id}",
|
|
card_type=CardType.POKEMON,
|
|
hp=90,
|
|
pokemon_type=EnergyType.LIGHTNING,
|
|
stage=PokemonStage.STAGE_1,
|
|
evolves_from="SomeBasic",
|
|
)
|
|
|
|
|
|
def make_trainer(card_id: str) -> CardDefinition:
|
|
"""Create a Trainer card definition."""
|
|
return CardDefinition(
|
|
id=card_id,
|
|
name=f"Trainer {card_id}",
|
|
card_type=CardType.TRAINER,
|
|
trainer_type="item",
|
|
)
|
|
|
|
|
|
def basic_pokemon_lookup(card_id: str) -> CardDefinition | None:
|
|
"""Card lookup that returns Basic Pokemon for any ID."""
|
|
return make_basic_pokemon(card_id)
|
|
|
|
|
|
def stage1_pokemon_lookup(card_id: str) -> CardDefinition | None:
|
|
"""Card lookup that returns Stage 1 Pokemon for any ID."""
|
|
return make_stage1_pokemon(card_id)
|
|
|
|
|
|
def trainer_lookup(card_id: str) -> CardDefinition | None:
|
|
"""Card lookup that returns Trainers for any ID."""
|
|
return make_trainer(card_id)
|
|
|
|
|
|
def null_lookup(card_id: str) -> CardDefinition | None:
|
|
"""Card lookup that returns None for any ID."""
|
|
return None
|
|
|
|
|
|
@pytest.fixture
|
|
def default_config() -> DeckConfig:
|
|
"""Create a default DeckConfig for testing."""
|
|
return DeckConfig()
|
|
|
|
|
|
# =============================================================================
|
|
# ValidationResult Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestValidationResult:
|
|
"""Tests for the ValidationResult dataclass."""
|
|
|
|
def test_default_is_valid(self):
|
|
"""Test that a new result starts as valid with no errors."""
|
|
result = ValidationResult()
|
|
|
|
assert result.is_valid is True
|
|
assert result.errors == []
|
|
|
|
def test_add_error_marks_invalid(self):
|
|
"""Test that adding an error marks the result as invalid."""
|
|
result = ValidationResult()
|
|
result.add_error("Test error")
|
|
|
|
assert result.is_valid is False
|
|
assert "Test error" in result.errors
|
|
|
|
def test_add_multiple_errors(self):
|
|
"""Test that multiple errors can be accumulated."""
|
|
result = ValidationResult()
|
|
result.add_error("Error 1")
|
|
result.add_error("Error 2")
|
|
result.add_error("Error 3")
|
|
|
|
assert result.is_valid is False
|
|
assert len(result.errors) == 3
|
|
|
|
|
|
# =============================================================================
|
|
# Card Count Validation Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestCardCountValidation:
|
|
"""Tests for main deck card count validation (40 cards required)."""
|
|
|
|
def test_valid_card_count_passes(self, default_config):
|
|
"""Test that exactly 40 cards passes validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)} # 40 cards
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "must have exactly 40 cards" not in str(result.errors)
|
|
|
|
def test_39_cards_fails(self, default_config):
|
|
"""Test that 39 cards fails validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(9)} # 36 cards
|
|
cards["card-extra"] = 3 # 39 total
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("got 39" in e for e in result.errors)
|
|
|
|
def test_41_cards_fails(self, default_config):
|
|
"""Test that 41 cards fails validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)} # 40 cards
|
|
cards["card-extra"] = 1 # 41 total
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("got 41" in e for e in result.errors)
|
|
|
|
def test_empty_deck_fails(self, default_config):
|
|
"""Test that an empty deck fails validation."""
|
|
result = validate_deck({}, {"lightning": 20}, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("got 0" in e for e in result.errors)
|
|
|
|
|
|
# =============================================================================
|
|
# Energy Count Validation Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestEnergyCountValidation:
|
|
"""Tests for energy deck card count validation (20 energy required)."""
|
|
|
|
def test_valid_energy_count_passes(self, default_config):
|
|
"""Test that exactly 20 energy cards passes validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 14, "colorless": 6} # 20 total
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "Energy deck must have exactly 20" not in str(result.errors)
|
|
|
|
def test_19_energy_fails(self, default_config):
|
|
"""Test that 19 energy cards fails validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 19}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("got 19" in e for e in result.errors)
|
|
|
|
def test_21_energy_fails(self, default_config):
|
|
"""Test that 21 energy cards fails validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 21}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("got 21" in e for e in result.errors)
|
|
|
|
|
|
class TestEnergyTypeValidation:
|
|
"""Tests for energy type name validation."""
|
|
|
|
def test_valid_energy_types_pass(self, default_config):
|
|
"""Test that valid energy type names pass validation.
|
|
|
|
Uses the standard EnergyType enum values (lowercase).
|
|
"""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"fire": 10, "water": 5, "grass": 5} # All valid types
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "Invalid energy types" not in str(result.errors)
|
|
|
|
def test_invalid_energy_type_fails(self, default_config):
|
|
"""Test that invalid energy type names fail validation.
|
|
|
|
Energy types must match EnergyType enum values (e.g., 'fire', not 'FIRE').
|
|
"""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"invalid_type": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("Invalid energy types" in e for e in result.errors)
|
|
assert any("invalid_type" in e for e in result.errors)
|
|
|
|
def test_multiple_invalid_energy_types_reported(self, default_config):
|
|
"""Test that multiple invalid energy types are all reported.
|
|
|
|
The error message should list up to 5 invalid types.
|
|
"""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"bad_type1": 10, "bad_type2": 5, "grass": 5} # 2 invalid
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
error_str = str(result.errors)
|
|
assert "bad_type1" in error_str
|
|
assert "bad_type2" in error_str
|
|
|
|
def test_case_sensitive_energy_types(self, default_config):
|
|
"""Test that energy type validation is case-sensitive.
|
|
|
|
EnergyType uses lowercase values, so 'FIRE' should fail.
|
|
"""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"FIRE": 10, "Fire": 10} # Both wrong case
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("Invalid energy types" in e for e in result.errors)
|
|
|
|
|
|
# =============================================================================
|
|
# Max Copies Per Card Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestMaxCopiesValidation:
|
|
"""Tests for maximum copies per card validation (4 max)."""
|
|
|
|
def test_4_copies_allowed(self, default_config):
|
|
"""Test that 4 copies of a card is allowed."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)} # 4 copies each
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "max allowed is 4" not in str(result.errors)
|
|
|
|
def test_5_copies_fails(self, default_config):
|
|
"""Test that 5 copies of a card fails validation."""
|
|
cards = {"over-limit": 5}
|
|
# Pad to 40 cards
|
|
for i in range(7):
|
|
cards[f"card-{i:03d}"] = 5
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("over-limit" in e and "5 copies" in e for e in result.errors)
|
|
|
|
def test_multiple_cards_over_limit(self, default_config):
|
|
"""Test that multiple cards over limit all get reported."""
|
|
cards = {"card-a": 5, "card-b": 6, "card-c": 4} # a and b over
|
|
cards["filler"] = 25
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
error_str = str(result.errors)
|
|
assert "card-a" in error_str
|
|
assert "card-b" in error_str
|
|
|
|
|
|
# =============================================================================
|
|
# Basic Pokemon Requirement Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestBasicPokemonRequirement:
|
|
"""Tests for minimum Basic Pokemon requirement (at least 1)."""
|
|
|
|
def test_deck_with_basic_pokemon_passes(self, default_config):
|
|
"""Test that a deck with Basic Pokemon passes validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "at least 1 Basic Pokemon" not in str(result.errors)
|
|
|
|
def test_deck_without_basic_pokemon_fails(self, default_config):
|
|
"""Test that a deck without Basic Pokemon fails validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, stage1_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("at least 1 Basic Pokemon" in e for e in result.errors)
|
|
|
|
def test_deck_with_only_trainers_fails(self, default_config):
|
|
"""Test that a deck with only Trainers fails Basic Pokemon check."""
|
|
cards = {f"trainer-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, trainer_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("at least 1 Basic Pokemon" in e for e in result.errors)
|
|
|
|
|
|
# =============================================================================
|
|
# Card ID Validation Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestCardIdValidation:
|
|
"""Tests for card ID existence validation."""
|
|
|
|
def test_valid_card_ids_pass(self, default_config):
|
|
"""Test that valid card IDs pass validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, basic_pokemon_lookup)
|
|
|
|
assert "Invalid card IDs" not in str(result.errors)
|
|
|
|
def test_invalid_card_id_fails(self, default_config):
|
|
"""Test that an invalid card ID fails validation."""
|
|
|
|
def partial_lookup(card_id: str) -> CardDefinition | None:
|
|
if card_id == "bad-card":
|
|
return None
|
|
return make_basic_pokemon(card_id)
|
|
|
|
cards = {"good-card": 4, "bad-card": 4}
|
|
for i in range(8):
|
|
cards[f"card-{i:03d}"] = 4
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, partial_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("Invalid card IDs" in e and "bad-card" in e for e in result.errors)
|
|
|
|
def test_multiple_invalid_ids_reported(self, default_config):
|
|
"""Test that multiple invalid IDs are reported together."""
|
|
|
|
def partial_lookup(card_id: str) -> CardDefinition | None:
|
|
if card_id.startswith("bad"):
|
|
return None
|
|
return make_basic_pokemon(card_id)
|
|
|
|
cards = {"bad-1": 4, "bad-2": 4, "bad-3": 4, "good": 28}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, default_config, partial_lookup)
|
|
|
|
assert result.is_valid is False
|
|
error_str = str(result.errors)
|
|
assert "bad-1" in error_str
|
|
assert "bad-2" in error_str
|
|
assert "bad-3" in error_str
|
|
|
|
|
|
# =============================================================================
|
|
# Ownership Validation Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestOwnershipValidation:
|
|
"""Tests for card ownership validation."""
|
|
|
|
def test_owned_cards_pass(self, default_config):
|
|
"""Test that deck passes when user owns all cards."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
owned = {f"card-{i:03d}": 10 for i in range(10)}
|
|
|
|
result = validate_deck(
|
|
cards, energy, default_config, basic_pokemon_lookup, owned_cards=owned
|
|
)
|
|
|
|
assert "Insufficient cards" not in str(result.errors)
|
|
|
|
def test_insufficient_ownership_fails(self, default_config):
|
|
"""Test that deck fails when user doesn't own enough copies."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
owned = {f"card-{i:03d}": 10 for i in range(10)}
|
|
owned["card-000"] = 2 # Need 4, only have 2
|
|
|
|
result = validate_deck(
|
|
cards, energy, default_config, basic_pokemon_lookup, owned_cards=owned
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert any("card-000" in e and "need 4" in e and "own 2" in e for e in result.errors)
|
|
|
|
def test_unowned_card_fails(self, default_config):
|
|
"""Test that deck fails when user doesn't own a card at all."""
|
|
cards = {"owned-card": 20, "unowned-card": 20}
|
|
energy = {"lightning": 20}
|
|
owned = {"owned-card": 20} # Missing unowned-card
|
|
|
|
result = validate_deck(
|
|
cards, energy, default_config, basic_pokemon_lookup, owned_cards=owned
|
|
)
|
|
|
|
assert result.is_valid is False
|
|
assert any("unowned-card" in e and "own 0" in e for e in result.errors)
|
|
|
|
def test_none_owned_cards_skips_ownership_check(self, default_config):
|
|
"""Test that passing None for owned_cards skips ownership validation."""
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(
|
|
cards, energy, default_config, basic_pokemon_lookup, owned_cards=None
|
|
)
|
|
|
|
assert "Insufficient cards" not in str(result.errors)
|
|
|
|
|
|
# =============================================================================
|
|
# Multiple Errors Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestMultipleErrors:
|
|
"""Tests for returning all errors at once."""
|
|
|
|
def test_multiple_errors_returned_together(self, default_config):
|
|
"""Test that multiple validation errors are all returned."""
|
|
cards = {"bad-card": 5} # Invalid ID + over copy limit + wrong count
|
|
energy = {"lightning": 10} # Only 10 (not 20)
|
|
owned = {}
|
|
|
|
result = validate_deck(cards, energy, default_config, null_lookup, owned_cards=owned)
|
|
|
|
assert result.is_valid is False
|
|
assert len(result.errors) >= 3
|
|
|
|
|
|
# =============================================================================
|
|
# Custom Config Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestCustomConfig:
|
|
"""Tests for using custom DeckConfig values."""
|
|
|
|
def test_custom_deck_size(self):
|
|
"""Test that custom deck size is respected."""
|
|
custom_config = DeckConfig(min_size=60, max_size=60)
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)} # 40 cards
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, custom_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("must have exactly 60 cards" in e for e in result.errors)
|
|
|
|
def test_custom_energy_size(self):
|
|
"""Test that custom energy deck size is respected."""
|
|
custom_config = DeckConfig(energy_deck_size=30)
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)}
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, custom_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("must have exactly 30" in e for e in result.errors)
|
|
|
|
def test_custom_max_copies(self):
|
|
"""Test that custom max copies per card is respected."""
|
|
custom_config = DeckConfig(max_copies_per_card=2)
|
|
cards = {f"card-{i:03d}": 4 for i in range(10)} # 4 copies each
|
|
energy = {"lightning": 20}
|
|
|
|
result = validate_deck(cards, energy, custom_config, basic_pokemon_lookup)
|
|
|
|
assert result.is_valid is False
|
|
assert any("max allowed is 2" in e for e in result.errors)
|
|
|
|
|
|
# =============================================================================
|
|
# Utility Function Tests
|
|
# =============================================================================
|
|
|
|
|
|
class TestUtilityFunctions:
|
|
"""Tests for utility functions."""
|
|
|
|
def test_validate_cards_exist_all_valid(self):
|
|
"""Test validate_cards_exist returns empty list when all valid."""
|
|
card_ids = ["card-1", "card-2", "card-3"]
|
|
|
|
invalid = validate_cards_exist(card_ids, basic_pokemon_lookup)
|
|
|
|
assert invalid == []
|
|
|
|
def test_validate_cards_exist_some_invalid(self):
|
|
"""Test validate_cards_exist returns invalid IDs."""
|
|
|
|
def partial_lookup(card_id: str) -> CardDefinition | None:
|
|
if card_id.startswith("bad"):
|
|
return None
|
|
return make_basic_pokemon(card_id)
|
|
|
|
card_ids = ["good-1", "bad-1", "good-2", "bad-2"]
|
|
|
|
invalid = validate_cards_exist(card_ids, partial_lookup)
|
|
|
|
assert set(invalid) == {"bad-1", "bad-2"}
|
|
|
|
def test_count_basic_pokemon(self):
|
|
"""Test count_basic_pokemon returns correct count."""
|
|
|
|
def mixed_lookup(card_id: str) -> CardDefinition | None:
|
|
if card_id.startswith("basic"):
|
|
return make_basic_pokemon(card_id)
|
|
elif card_id.startswith("stage1"):
|
|
return make_stage1_pokemon(card_id)
|
|
else:
|
|
return make_trainer(card_id)
|
|
|
|
cards = {"basic-1": 4, "basic-2": 3, "stage1-1": 4, "trainer-1": 4}
|
|
|
|
count = count_basic_pokemon(cards, mixed_lookup)
|
|
|
|
assert count == 7 # basic-1: 4 + basic-2: 3
|
|
|
|
def test_validate_energy_types_all_valid(self):
|
|
"""Test validate_energy_types returns empty list when all valid."""
|
|
energy_types = ["fire", "water", "grass", "colorless"]
|
|
|
|
invalid = validate_energy_types(energy_types)
|
|
|
|
assert invalid == []
|
|
|
|
def test_validate_energy_types_some_invalid(self):
|
|
"""Test validate_energy_types returns invalid types."""
|
|
energy_types = ["fire", "bad_type", "water", "invalid"]
|
|
|
|
invalid = validate_energy_types(energy_types)
|
|
|
|
assert set(invalid) == {"bad_type", "invalid"}
|
|
|
|
def test_valid_energy_types_constant(self):
|
|
"""Test VALID_ENERGY_TYPES matches EnergyType enum."""
|
|
expected = {
|
|
"colorless",
|
|
"darkness",
|
|
"dragon",
|
|
"fighting",
|
|
"fire",
|
|
"grass",
|
|
"lightning",
|
|
"metal",
|
|
"psychic",
|
|
"water",
|
|
}
|
|
|
|
assert expected == VALID_ENERGY_TYPES
|