import io import pandas as pd import pytest from datetime import date, time from scenar.core import ( read_excel, create_timetable, get_program_types, ScenarsError, parse_inline_schedule, parse_inline_types, ValidationError ) def make_excel_bytes(df: pd.DataFrame) -> bytes: bio = io.BytesIO() with pd.ExcelWriter(bio, engine='openpyxl') as writer: df.to_excel(writer, index=False) return bio.getvalue() def test_read_excel_happy_path(): df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': ['09:00'], 'Konec': ['10:00'], 'Program': ['Test program'], 'Typ': ['WORKSHOP'], 'Garant': ['Garant Name'], 'Poznamka': ['Pozn'] }) content = make_excel_bytes(df) valid, errors = read_excel(content) assert isinstance(valid, pd.DataFrame) assert len(errors) == 0 assert len(valid) == 1 assert valid.iloc[0]['Program'] == 'Test program' def test_read_excel_invalid_time(): df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': ['not-a-time'], 'Konec': ['10:00'], 'Program': ['Bad Time'], 'Typ': ['LECTURE'], 'Garant': [None], 'Poznamka': [None] }) content = make_excel_bytes(df) valid, errors = read_excel(content) assert isinstance(errors, list) assert len(errors) >= 1 def test_get_program_types(): """Test form field parsing for program type/color/description.""" form_data = { 'type_code_0': 'WORKSHOP', 'type_code_1': 'LECTURE', 'desc_0': 'Workshop description', 'desc_1': 'Lecture description', 'color_0': '#FF0000', 'color_1': '#00FF00', } # get_program_types returns (descriptions_dict, colors_dict) tuple descriptions, colors = get_program_types(form_data) assert len(descriptions) == 2 assert descriptions['WORKSHOP'] == 'Workshop description' assert descriptions['LECTURE'] == 'Lecture description' assert colors['WORKSHOP'] == 'FFFF0000' # FF prefix added assert colors['LECTURE'] == 'FF00FF00' def test_create_timetable(): """Test Excel timetable generation with properly parsed times.""" from datetime import time # Create test data with time objects (as returned by read_excel) df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': [time(9, 0)], # Use time object, not string 'Konec': [time(10, 0)], 'Program': ['Test Program'], 'Typ': ['WORKSHOP'], 'Garant': ['John Doe'], 'Poznamka': ['Test note'], }) program_descriptions = { 'WORKSHOP': 'Workshop Type', } program_colors = { 'WORKSHOP': 'FF0070C0', # AARRGGBB format } # create_timetable returns openpyxl.Workbook wb = create_timetable( df, title="Test Timetable", detail="Test Detail", program_descriptions=program_descriptions, program_colors=program_colors ) assert wb is not None assert len(wb.sheetnames) > 0 ws = wb.active assert ws['A1'].value == "Test Timetable" assert ws['A2'].value == "Test Detail" def test_create_timetable_with_color_dict(): """Test timetable generation with separate color dict.""" from datetime import time df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': [time(9, 0)], # Use time object 'Konec': [time(10, 0)], 'Program': ['Lecture 101'], 'Typ': ['LECTURE'], 'Garant': ['Dr. Smith'], 'Poznamka': [None], }) program_descriptions = { 'LECTURE': 'Standard Lecture', } program_colors = { 'LECTURE': 'FFFF6600', } wb = create_timetable( df, title="Advanced Timetable", detail="With color dict", program_descriptions=program_descriptions, program_colors=program_colors ) assert wb is not None ws = wb.active assert ws['A1'].value == "Advanced Timetable" def test_parse_inline_schedule(): """Test parsing inline schedule form data.""" form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Test Program', 'typ_0': 'WORKSHOP', 'garant_0': 'John Doe', 'poznamka_0': 'Test note', 'datum_1': '2025-11-13', 'zacatek_1': '10:30', 'konec_1': '11:30', 'program_1': 'Another Program', 'typ_1': 'LECTURE', 'garant_1': 'Jane Smith', 'poznamka_1': '', } df = parse_inline_schedule(form_data) assert isinstance(df, pd.DataFrame) assert len(df) == 2 assert df.iloc[0]['Program'] == 'Test Program' assert df.iloc[0]['Typ'] == 'WORKSHOP' assert df.iloc[1]['Program'] == 'Another Program' assert df.iloc[1]['Typ'] == 'LECTURE' def test_parse_inline_schedule_missing_required(): """Test that missing required fields raise ValidationError.""" form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', # Missing konec_0, program_0, typ_0 } with pytest.raises(ValidationError): parse_inline_schedule(form_data) def test_parse_inline_types(): """Test parsing inline type definitions.""" form_data = { 'type_name_0': 'WORKSHOP', 'type_desc_0': 'Workshop Type', 'type_color_0': '#0070C0', 'type_name_1': 'LECTURE', 'type_desc_1': 'Lecture Type', 'type_color_1': '#FF6600', } descriptions, colors = parse_inline_types(form_data) assert len(descriptions) == 2 assert descriptions['WORKSHOP'] == 'Workshop Type' assert descriptions['LECTURE'] == 'Lecture Type' assert colors['WORKSHOP'] == 'FF0070C0' assert colors['LECTURE'] == 'FFFF6600' def test_inline_workflow_integration(): """Test end-to-end inline workflow: parse form → create timetable.""" # Schedule form data schedule_form = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Opening', 'typ_0': 'KEYNOTE', 'garant_0': 'Dr. Smith', 'poznamka_0': 'Start of event', } # Type form data types_form = { 'type_name_0': 'KEYNOTE', 'type_desc_0': 'Keynote Speech', 'type_color_0': '#FF0000', } # Merge forms form_data = {**schedule_form, **types_form} # Parse df = parse_inline_schedule(form_data) descriptions, colors = parse_inline_types(form_data) # Generate timetable wb = create_timetable( df, title="Integration Test Event", detail="Testing inline workflow", program_descriptions=descriptions, program_colors=colors ) assert wb is not None ws = wb.active assert ws['A1'].value == "Integration Test Event" assert ws['A2'].value == "Testing inline workflow"