""" Integration tests for E2E Bottle Line ControlPlan scenario. These tests verify: 1. The example control plan compiles to valid Python 2. Generated HIL files have correct structure 3. Validation mode passes 4. Generated code can be parsed by Python's ast module No Docker or external services required. """ import ast import tempfile from pathlib import Path import pytest from models.control_plan import ControlPlan from tools.compile_control_plan import compile_control_plan, validate_control_plan # Path to the example control plan EXAMPLE_CONTROL_PLAN = Path(__file__).parent.parent / "examples" / "control_plans" / "bottle_line_v0.1.json" class TestBottleLineControlPlan: """Test the bottle_line_v0.1.json control plan.""" def test_example_exists(self): """Verify the example control plan file exists.""" assert EXAMPLE_CONTROL_PLAN.exists(), f"Example not found: {EXAMPLE_CONTROL_PLAN}" def test_loads_as_valid_control_plan(self): """Verify the example loads as a valid ControlPlan.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) assert plan.version == "v0.1" assert len(plan.hils) == 2 assert plan.hils[0].name == "water_hil" assert plan.hils[1].name == "filler_hil" def test_validation_passes(self): """Verify validation passes with no errors.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) errors = validate_control_plan(plan) assert errors == [], f"Validation errors: {errors}" def test_compiles_to_valid_python(self): """Verify compilation produces syntactically valid Python.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) # Should have 2 HIL files assert len(result) == 2 assert "water_hil" in result assert "filler_hil" in result # Each should be valid Python for hil_name, code in result.items(): try: ast.parse(code) except SyntaxError as e: pytest.fail(f"Syntax error in {hil_name}: {e}") def test_generated_code_has_logic_function(self): """Verify generated code contains logic(physical_values) function.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) for hil_name, code in result.items(): assert "def logic(physical_values):" in code, \ f"{hil_name} missing logic(physical_values)" def test_generated_code_has_while_true(self): """Verify generated code contains while True loop.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) for hil_name, code in result.items(): assert "while True:" in code, \ f"{hil_name} missing while True loop" def test_generated_code_has_time_sleep(self): """Verify generated code contains time.sleep calls.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) for hil_name, code in result.items(): assert "time.sleep(" in code, \ f"{hil_name} missing time.sleep" def test_warmup_delay_included(self): """Verify warmup delay is included in generated code.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) # Both HILs have warmup_s: 3.0 for hil_name, code in result.items(): assert "time.sleep(3.0)" in code, \ f"{hil_name} missing warmup delay" def test_writes_to_temp_directory(self): """Verify compiled files can be written to disk.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) with tempfile.TemporaryDirectory() as tmpdir: tmppath = Path(tmpdir) for hil_name, code in result.items(): out_file = tmppath / f"{hil_name}.py" out_file.write_text(code, encoding="utf-8") assert out_file.exists() # Read back and verify content = out_file.read_text(encoding="utf-8") assert "def logic(physical_values):" in content def test_water_hil_initializes_tank_level(self): """Verify water_hil initializes water_tank_level.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) water_code = result["water_hil"] assert "pv['water_tank_level'] = 500" in water_code, \ "water_hil should initialize water_tank_level to 500" def test_filler_hil_initializes_bottle_fill_level(self): """Verify filler_hil initializes bottle_fill_level.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) filler_code = result["filler_hil"] assert "pv['bottle_fill_level'] = 0" in filler_code, \ "filler_hil should initialize bottle_fill_level to 0" def test_clamp_function_included(self): """Verify clamp helper function is included.""" import json data = json.loads(EXAMPLE_CONTROL_PLAN.read_text(encoding="utf-8")) plan = ControlPlan.model_validate(data) result = compile_control_plan(plan) for hil_name, code in result.items(): assert "def clamp(x, lo, hi):" in code, \ f"{hil_name} missing clamp function" class TestPromptFileExists: """Test that the e2e prompt file exists.""" def test_e2e_bottle_prompt_exists(self): """Verify prompts/e2e_bottle.txt exists.""" prompt_file = Path(__file__).parent.parent / "prompts" / "e2e_bottle.txt" assert prompt_file.exists(), f"Prompt not found: {prompt_file}" def test_e2e_bottle_prompt_has_content(self): """Verify prompt file has meaningful content.""" prompt_file = Path(__file__).parent.parent / "prompts" / "e2e_bottle.txt" content = prompt_file.read_text(encoding="utf-8") # Should mention the two HILs assert "water_hil" in content assert "filler_hil" in content # Should mention the two PLCs assert "plc1" in content.lower() or "PLC1" in content assert "plc2" in content.lower() or "PLC2" in content class TestE2EScriptExists: """Test that the E2E script exists and is executable.""" def test_e2e_script_exists(self): """Verify scripts/e2e_bottle_control_plan.sh exists.""" script_file = Path(__file__).parent.parent / "scripts" / "e2e_bottle_control_plan.sh" assert script_file.exists(), f"Script not found: {script_file}" def test_e2e_script_is_executable(self): """Verify script has executable permission.""" import os script_file = Path(__file__).parent.parent / "scripts" / "e2e_bottle_control_plan.sh" assert os.access(script_file, os.X_OK), f"Script not executable: {script_file}" def test_e2e_script_has_shebang(self): """Verify script starts with proper shebang.""" script_file = Path(__file__).parent.parent / "scripts" / "e2e_bottle_control_plan.sh" content = script_file.read_text(encoding="utf-8") assert content.startswith("#!/bin/bash"), "Script missing bash shebang"