wall-construction-demo / tests /unit /test_simulator.py
AIVLAD's picture
test: fix test assertions for Pydantic migration
ebb16fa
"""Unit tests for wall construction simulator."""
from __future__ import annotations
from datetime import date
from decimal import Decimal
from pathlib import Path
import pytest
from apps.profiles.models import DailyProgress, Profile, WallSection
from apps.profiles.services.simulator import ProfileConfig, SectionData, WallSimulator
@pytest.mark.django_db
class TestWallSimulator:
"""Test cases for WallSimulator."""
def test_initialize_single_profile(self, tmp_path: Path) -> None:
"""Test initializing simulator with single profile."""
profiles_config = [ProfileConfig(heights=[21, 25, 28])]
simulator = WallSimulator(num_teams=2, log_dir=str(tmp_path))
section_data = simulator._initialize_profiles(profiles_config)
assert len(section_data) == 3
assert Profile.objects.count() == 1
assert WallSection.objects.count() == 3
profile = Profile.objects.first()
assert profile is not None
assert profile.name == "Profile 1"
def test_initialize_multiple_profiles(self, tmp_path: Path) -> None:
"""Test initializing simulator with multiple profiles."""
profiles_config = [
ProfileConfig(heights=[21, 25, 28]),
ProfileConfig(heights=[17]),
ProfileConfig(heights=[17, 22, 17, 19, 17]),
]
simulator = WallSimulator(num_teams=4, log_dir=str(tmp_path))
section_data = simulator._initialize_profiles(profiles_config)
assert len(section_data) == 9
assert Profile.objects.count() == 3
assert WallSection.objects.count() == 9
def test_assign_work_limits_by_teams(self, tmp_path: Path) -> None:
"""Test that work assignment respects team limit."""
section_data = [
SectionData(
id=i,
profile_id=1,
profile_name="Profile 1",
section_name=f"Section {i}",
current_height=20,
)
for i in range(10)
]
simulator = WallSimulator(num_teams=3, log_dir=str(tmp_path))
assigned = simulator._assign_work(section_data)
assert len(assigned) == 3
def test_assign_work_skips_completed_sections(self, tmp_path: Path) -> None:
"""Test that completed sections are not assigned."""
section_data = [
SectionData(id=1, profile_id=1, profile_name="Profile 1", section_name="Section 1", current_height=30),
SectionData(id=2, profile_id=1, profile_name="Profile 1", section_name="Section 2", current_height=25),
SectionData(id=3, profile_id=1, profile_name="Profile 1", section_name="Section 3", current_height=30),
SectionData(id=4, profile_id=1, profile_name="Profile 1", section_name="Section 4", current_height=28),
]
simulator = WallSimulator(num_teams=5, log_dir=str(tmp_path))
assigned = simulator._assign_work(section_data)
assert len(assigned) == 2
assert all(s.current_height < 30 for s in assigned)
def test_process_section_calculates_correctly(self, tmp_path: Path) -> None:
"""Test that section processing calculates ice and cost correctly."""
section = SectionData(
id=1,
profile_id=1,
profile_name="Profile 1",
section_name="Section 1",
current_height=20,
)
simulator = WallSimulator(num_teams=1, log_dir=str(tmp_path))
result = simulator._process_section(section, day=1, team_id=0)
assert result.section_id == 1
assert result.feet_built == Decimal("1")
assert result.ice == Decimal("195")
assert result.cost == Decimal("370500")
def test_process_section_writes_log(self, tmp_path: Path) -> None:
"""Test that section processing writes to log file."""
section = SectionData(
id=1,
profile_id=1,
profile_name="Profile 1",
section_name="Section 1",
current_height=20,
)
simulator = WallSimulator(num_teams=1, log_dir=str(tmp_path))
simulator._process_section(section, day=1, team_id=0)
log_file = tmp_path / "team_0.log"
assert log_file.exists()
log_content = log_file.read_text()
assert "Day 1" in log_content
assert "Section 1" in log_content
def test_process_section_logs_completion(self, tmp_path: Path) -> None:
"""Test that section completion is logged."""
section = SectionData(
id=1,
profile_id=1,
profile_name="Profile 1",
section_name="Section 1",
current_height=29,
)
simulator = WallSimulator(num_teams=1, log_dir=str(tmp_path))
simulator._process_section(section, day=1, team_id=0)
log_file = tmp_path / "team_0.log"
log_content = log_file.read_text()
assert "completed" in log_content
def test_simulate_simple_profile(self, tmp_path: Path) -> None:
"""Test complete simulation with simple profile."""
profiles_config = [ProfileConfig(heights=[28, 29])]
simulator = WallSimulator(num_teams=2, log_dir=str(tmp_path))
result = simulator.simulate(profiles_config, date(2025, 10, 20))
assert result.total_days == 2
assert result.total_sections == 2
# 3 DailyProgress records: Day 1 both sections (28→29, 29→30), Day 2 first section only (29→30)
assert DailyProgress.objects.count() == 3
profile = Profile.objects.first()
assert profile is not None
sections = WallSection.objects.filter(profile=profile)
for section in sections:
assert section.current_height == 30
def test_simulate_creates_logs(self, tmp_path: Path) -> None:
"""Test that simulation creates team log files."""
profiles_config = [ProfileConfig(heights=[28])]
simulator = WallSimulator(num_teams=2, log_dir=str(tmp_path))
simulator.simulate(profiles_config, date(2025, 10, 20))
assert (tmp_path / "team_0.log").exists()
assert (tmp_path / "team_1.log").exists()
log_content = (tmp_path / "team_0.log").read_text()
assert "relieved" in log_content
def test_simulate_respects_team_limit(self, tmp_path: Path) -> None:
"""Test that simulation respects team limit."""
profiles_config = [ProfileConfig(heights=[25, 25, 25, 25, 25])]
simulator = WallSimulator(num_teams=2, log_dir=str(tmp_path))
simulator.simulate(profiles_config, date(2025, 10, 20))
progress_by_day: dict[str, int] = {}
for progress in DailyProgress.objects.all():
day_key = progress.date.isoformat()
progress_by_day[day_key] = progress_by_day.get(day_key, 0) + 1
for count in progress_by_day.values():
assert count <= 2
def test_simulate_with_varying_heights(self, tmp_path: Path) -> None:
"""Test simulation with varying initial heights."""
profiles_config = [ProfileConfig(heights=[21, 25, 28, 30, 17])]
simulator = WallSimulator(num_teams=3, log_dir=str(tmp_path))
result = simulator.simulate(profiles_config, date(2025, 10, 20))
assert result.total_sections == 5
sections_with_height_30 = WallSection.objects.filter(initial_height=30, current_height=30).count()
assert sections_with_height_30 == 1
all_sections = WallSection.objects.all()
for section in all_sections:
assert section.current_height == 30