Expand services/naming.py with suggest_names() that picks random words
from a category while excluding nicknames already used in the run. Add
GET /runs/{run_id}/name-suggestions?count=10 endpoint that reads the
run's naming_scheme and returns filtered suggestions. Includes 12 unit
tests covering selection, exclusion, exhaustion, and cross-category
independence.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.9 KiB
Python
85 lines
2.9 KiB
Python
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from app.services.naming import (
|
|
get_naming_categories,
|
|
get_words_for_category,
|
|
suggest_names,
|
|
)
|
|
|
|
MOCK_DICTIONARY = {
|
|
"mythology": ["Apollo", "Athena", "Loki", "Thor", "Zeus"],
|
|
"food": ["Basil", "Sage", "Pepper", "Saffron", "Mango"],
|
|
"space": ["Apollo", "Nova", "Nebula", "Comet", "Vega"],
|
|
}
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _mock_dictionary():
|
|
with patch("app.services.naming._load_dictionary", return_value=MOCK_DICTIONARY):
|
|
yield
|
|
|
|
|
|
class TestGetNamingCategories:
|
|
def test_returns_sorted_categories(self):
|
|
result = get_naming_categories()
|
|
assert result == ["food", "mythology", "space"]
|
|
|
|
def test_returns_empty_for_empty_dictionary(self):
|
|
with patch("app.services.naming._load_dictionary", return_value={}):
|
|
assert get_naming_categories() == []
|
|
|
|
|
|
class TestGetWordsForCategory:
|
|
def test_returns_words_for_valid_category(self):
|
|
result = get_words_for_category("mythology")
|
|
assert result == ["Apollo", "Athena", "Loki", "Thor", "Zeus"]
|
|
|
|
def test_returns_empty_for_unknown_category(self):
|
|
assert get_words_for_category("nonexistent") == []
|
|
|
|
|
|
class TestSuggestNames:
|
|
def test_returns_requested_count(self):
|
|
result = suggest_names("mythology", set(), count=3)
|
|
assert len(result) == 3
|
|
assert all(name in MOCK_DICTIONARY["mythology"] for name in result)
|
|
|
|
def test_excludes_used_names(self):
|
|
used = {"Apollo", "Athena", "Loki"}
|
|
result = suggest_names("mythology", used, count=10)
|
|
assert set(result) <= {"Thor", "Zeus"}
|
|
assert not set(result) & used
|
|
|
|
def test_returns_fewer_when_category_nearly_exhausted(self):
|
|
used = {"Apollo", "Athena", "Loki", "Thor"}
|
|
result = suggest_names("mythology", used, count=10)
|
|
assert result == ["Zeus"]
|
|
|
|
def test_returns_empty_when_category_fully_exhausted(self):
|
|
used = {"Apollo", "Athena", "Loki", "Thor", "Zeus"}
|
|
result = suggest_names("mythology", used, count=10)
|
|
assert result == []
|
|
|
|
def test_returns_empty_for_unknown_category(self):
|
|
result = suggest_names("nonexistent", set(), count=10)
|
|
assert result == []
|
|
|
|
def test_no_duplicates_in_suggestions(self):
|
|
result = suggest_names("mythology", set(), count=5)
|
|
assert len(result) == len(set(result))
|
|
|
|
def test_default_count_is_ten(self):
|
|
# food has 5 words, so we should get all 5
|
|
result = suggest_names("food", set())
|
|
assert len(result) == 5
|
|
|
|
def test_cross_category_names_handled_independently(self):
|
|
# "Apollo" used in mythology shouldn't affect space
|
|
used = {"Apollo"}
|
|
mythology_result = suggest_names("mythology", used, count=10)
|
|
space_result = suggest_names("space", used, count=10)
|
|
assert "Apollo" not in mythology_result
|
|
assert "Apollo" not in space_result
|