feat(backend): create improved tests

This commit is contained in:
2025-08-29 23:50:44 -04:00
parent 1dbfbb9ce6
commit b25191d99a
9 changed files with 3031 additions and 177 deletions

View File

@@ -1,9 +1,10 @@
# backend/tests/repositories/test_classification.py
# ------------------------------------------------------------
# Pytest suite for the ClassificationRepository.
# Comprehensive pytest suite for the ClassificationRepository.
# ------------------------------------------------------------
import pytest
from typing import List
from backend.models import Classification as ClassificationModel
from backend.repositories import ClassificationRepository
@@ -90,4 +91,383 @@ def test_delete(classification_repo):
remaining_names = {r.ClassificationName for r in remaining}
assert "TempVoice" not in remaining_names
# the original four seeded names must still be present
assert {"Soprano", "Alto / Mezzo", "Tenor", "Baritone"} <= remaining_names
assert {"Soprano", "Alto / Mezzo", "Tenor", "Baritone"} <= remaining_names
# ----------------------------------------------------------------------
# 6⃣ Edge cases and error conditions
# ----------------------------------------------------------------------
def test_get_by_id_returns_none_when_missing(classification_repo: ClassificationRepository):
"""Test that get_by_id returns None for nonexistent IDs."""
result = classification_repo.get_by_id(99999)
assert result is None
def test_get_by_id_with_negative_id(classification_repo: ClassificationRepository):
"""Test get_by_id with negative ID (should return None)."""
result = classification_repo.get_by_id(-1)
assert result is None
def test_get_by_id_with_zero_id(classification_repo: ClassificationRepository):
"""Test get_by_id with zero ID (should return None)."""
result = classification_repo.get_by_id(0)
assert result is None
def test_find_by_name_case_sensitivity(classification_repo: ClassificationRepository):
"""Test that find_by_name is case-sensitive."""
# Exact case should work
exact = classification_repo.find_by_name("Soprano")
assert exact is not None
assert exact.ClassificationName == "Soprano"
# Different cases should return None
assert classification_repo.find_by_name("soprano") is None
assert classification_repo.find_by_name("SOPRANO") is None
assert classification_repo.find_by_name("SoPrAnO") is None
def test_find_by_name_with_whitespace(classification_repo: ClassificationRepository):
"""Test find_by_name behavior with whitespace variations."""
# Exact name with spaces should work
exact = classification_repo.find_by_name("Alto / Mezzo")
assert exact is not None
# Names with extra whitespace should return None (no trimming)
assert classification_repo.find_by_name(" Alto / Mezzo") is None
assert classification_repo.find_by_name("Alto / Mezzo ") is None
assert classification_repo.find_by_name(" Alto / Mezzo ") is None
def test_find_by_name_with_empty_string(classification_repo: ClassificationRepository):
"""Test find_by_name with empty string."""
result = classification_repo.find_by_name("")
assert result is None
def test_create_with_empty_string_name(classification_repo: ClassificationRepository):
"""Test creating a classification with empty string name."""
# This should work - empty string is a valid name
empty_name = classification_repo.create("")
assert empty_name.ClassificationName == ""
assert isinstance(empty_name.ClassificationId, int)
assert empty_name.ClassificationId > 0
# Should be able to find it back
found = classification_repo.find_by_name("")
assert found is not None
assert found.ClassificationId == empty_name.ClassificationId
def test_create_with_whitespace_only_name(classification_repo: ClassificationRepository):
"""Test creating a classification with whitespace-only name."""
whitespace_name = classification_repo.create(" ")
assert whitespace_name.ClassificationName == " "
assert isinstance(whitespace_name.ClassificationId, int)
# Should be findable
found = classification_repo.find_by_name(" ")
assert found is not None
assert found.ClassificationId == whitespace_name.ClassificationId
def test_create_with_very_long_name(classification_repo: ClassificationRepository):
"""Test creating a classification with a very long name."""
long_name = "A" * 1000 # 1000 character name
long_classification = classification_repo.create(long_name)
assert long_classification.ClassificationName == long_name
assert isinstance(long_classification.ClassificationId, int)
# Should be findable
found = classification_repo.find_by_name(long_name)
assert found is not None
assert found.ClassificationId == long_classification.ClassificationId
def test_create_with_special_characters(classification_repo: ClassificationRepository):
"""Test creating classifications with special characters."""
special_names = [
"Alto/Soprano",
"Bass-Baritone",
"Counter-tenor (High)",
"Mezzo@Soprano",
"Coloratura Soprano (1st)",
"Basso Profondo & Cantante",
"Soprano (🎵)",
"Tenor - Lyric/Dramatic",
]
created_ids = []
for name in special_names:
classification = classification_repo.create(name)
assert classification.ClassificationName == name
assert isinstance(classification.ClassificationId, int)
created_ids.append(classification.ClassificationId)
# Should be findable
found = classification_repo.find_by_name(name)
assert found is not None
assert found.ClassificationId == classification.ClassificationId
# All IDs should be unique
assert len(set(created_ids)) == len(created_ids)
def test_delete_nonexistent_classification(classification_repo: ClassificationRepository):
"""Test deleting a classification that doesn't exist (should not raise error)."""
initial_count = len(classification_repo.list_all())
# This should not raise an exception
classification_repo.delete(99999)
# Count should remain the same
final_count = len(classification_repo.list_all())
assert final_count == initial_count
def test_delete_with_negative_id(classification_repo: ClassificationRepository):
"""Test delete with negative ID (should not raise error)."""
initial_count = len(classification_repo.list_all())
# This should not raise an exception
classification_repo.delete(-1)
# Count should remain the same
final_count = len(classification_repo.list_all())
assert final_count == initial_count
# ----------------------------------------------------------------------
# 7⃣ Data integrity and consistency tests
# ----------------------------------------------------------------------
def test_list_all_ordering_consistency(classification_repo: ClassificationRepository):
"""Test that list_all always returns results in consistent alphabetical order."""
# Add some classifications with names that test alphabetical ordering
test_names = ["Zebra", "Alpha", "Beta", "Zulu", "Apple", "Banana"]
created = []
for name in test_names:
created.append(classification_repo.create(name))
# Get all classifications multiple times
for _ in range(3):
all_classifications = classification_repo.list_all()
names = [c.ClassificationName for c in all_classifications]
# Should be in alphabetical order
assert names == sorted(names)
# Should contain our test names
for name in test_names:
assert name in names
def test_ensure_exists_idempotency_stress_test(classification_repo: ClassificationRepository):
"""Test that ensure_exists is truly idempotent under multiple calls."""
name = "StressTestClassification"
# Call ensure_exists multiple times
results = []
for _ in range(10):
result = classification_repo.ensure_exists(name)
results.append(result)
# All results should be the same object (same ID)
first_id = results[0].ClassificationId
for result in results:
assert result.ClassificationId == first_id
assert result.ClassificationName == name
# Should only exist once in the database
all_classifications = classification_repo.list_all()
matching = [c for c in all_classifications if c.ClassificationName == name]
assert len(matching) == 1
def test_classification_model_data_integrity(classification_repo: ClassificationRepository):
"""Test that Classification model preserves data integrity."""
original_name = "DataIntegrityTest"
classification = classification_repo.create(original_name)
# Verify original data
assert classification.ClassificationName == original_name
original_id = classification.ClassificationId
# Retrieve and verify data is preserved
retrieved = classification_repo.get_by_id(original_id)
assert retrieved is not None
assert retrieved.ClassificationId == original_id
assert retrieved.ClassificationName == original_name
# Verify through find_by_name as well
found_by_name = classification_repo.find_by_name(original_name)
assert found_by_name is not None
assert found_by_name.ClassificationId == original_id
assert found_by_name.ClassificationName == original_name
# ----------------------------------------------------------------------
# 8⃣ Parameterized tests for comprehensive coverage
# ----------------------------------------------------------------------
@pytest.mark.parametrize(
"test_name,expected_found",
[
("Soprano", True),
("Alto / Mezzo", True),
("Tenor", True),
("Baritone", True),
("Bass", False),
("Countertenor", False),
("Mezzo-Soprano", False),
("", False),
("soprano", False), # case sensitivity
("SOPRANO", False), # case sensitivity
]
)
def test_find_by_name_comprehensive(
classification_repo: ClassificationRepository,
test_name: str,
expected_found: bool
):
"""Comprehensive test of find_by_name with various inputs."""
result = classification_repo.find_by_name(test_name)
if expected_found:
assert result is not None
assert result.ClassificationName == test_name
assert isinstance(result.ClassificationId, int)
assert result.ClassificationId > 0
else:
assert result is None
@pytest.mark.parametrize(
"test_name",
[
"NewClassification1",
"Test With Spaces",
"Special-Characters!@#",
"123NumbersFirst",
"Mixed123Characters",
"Très_French_Ñame",
"Multi\nLine\nName",
"Tab\tSeparated",
"Quote'Name",
'Double"Quote"Name',
]
)
def test_create_and_retrieve_various_names(classification_repo: ClassificationRepository, test_name: str):
"""Test creating and retrieving classifications with various name formats."""
# Create
created = classification_repo.create(test_name)
assert created.ClassificationName == test_name
assert isinstance(created.ClassificationId, int)
assert created.ClassificationId > 0
# Retrieve by ID
by_id = classification_repo.get_by_id(created.ClassificationId)
assert by_id is not None
assert by_id.ClassificationName == test_name
assert by_id.ClassificationId == created.ClassificationId
# Retrieve by name
by_name = classification_repo.find_by_name(test_name)
assert by_name is not None
assert by_name.ClassificationName == test_name
assert by_name.ClassificationId == created.ClassificationId
# ----------------------------------------------------------------------
# 9⃣ Integration and workflow tests
# ----------------------------------------------------------------------
def test_complete_classification_workflow(classification_repo: ClassificationRepository):
"""Test a complete workflow with multiple operations."""
initial_count = len(classification_repo.list_all())
# Step 1: Create a new classification
new_name = "WorkflowTest"
created = classification_repo.create(new_name)
assert created.ClassificationName == new_name
# Step 2: Verify it exists in list_all
all_classifications = classification_repo.list_all()
assert len(all_classifications) == initial_count + 1
assert new_name in [c.ClassificationName for c in all_classifications]
# Step 3: Find by name
found = classification_repo.find_by_name(new_name)
assert found is not None
assert found.ClassificationId == created.ClassificationId
# Step 4: Get by ID
by_id = classification_repo.get_by_id(created.ClassificationId)
assert by_id is not None
assert by_id.ClassificationName == new_name
# Step 5: Use ensure_exists (should return existing)
ensured = classification_repo.ensure_exists(new_name)
assert ensured.ClassificationId == created.ClassificationId
# Step 6: Delete it
classification_repo.delete(created.ClassificationId)
# Step 7: Verify it's gone
assert classification_repo.get_by_id(created.ClassificationId) is None
assert classification_repo.find_by_name(new_name) is None
final_all = classification_repo.list_all()
assert len(final_all) == initial_count
assert new_name not in [c.ClassificationName for c in final_all]
def test_multiple_classifications_with_similar_names(classification_repo: ClassificationRepository):
"""Test handling of classifications with similar but distinct names."""
base_name = "TestSimilar"
similar_names = [
base_name,
base_name + " ", # with trailing space
" " + base_name, # with leading space
base_name.upper(), # different case
base_name.lower(), # different case
base_name + "2", # with number
base_name + "_Alt", # with suffix
]
created_classifications = []
for name in similar_names:
classification = classification_repo.create(name)
created_classifications.append(classification)
assert classification.ClassificationName == name
# All should have unique IDs
ids = [c.ClassificationId for c in created_classifications]
assert len(set(ids)) == len(ids)
# All should be findable by their exact names
for i, name in enumerate(similar_names):
found = classification_repo.find_by_name(name)
assert found is not None
assert found.ClassificationId == created_classifications[i].ClassificationId
assert found.ClassificationName == name
def test_classification_repository_thread_safety_simulation(classification_repo: ClassificationRepository):
"""Simulate concurrent operations to test repository consistency."""
# This simulates what might happen if multiple threads/processes were accessing the repo
base_name = "ConcurrencyTest"
# Simulate multiple "threads" trying to ensure the same classification exists
results = []
for i in range(5):
result = classification_repo.ensure_exists(base_name)
results.append(result)
# All should return the same classification
first_id = results[0].ClassificationId
for result in results:
assert result.ClassificationId == first_id
assert result.ClassificationName == base_name
# Should only exist once in the database
all_matches = [c for c in classification_repo.list_all() if c.ClassificationName == base_name]
assert len(all_matches) == 1