""" Core business logic tests — adapted from original test_read_excel.py and test_inline_builder.py. Tests the refactored app.core modules. """ import io import pandas as pd import pytest from datetime import date, time from app.core import ( read_excel, create_timetable, get_program_types, ScenarsError, parse_inline_schedule, parse_inline_types, ValidationError, validate_inputs, normalize_time, ) 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() # --- Validator tests --- def test_validate_inputs_valid(): validate_inputs("Title", "Detail", 100) def test_validate_inputs_empty_title(): with pytest.raises(ValidationError): validate_inputs("", "Detail", 100) def test_validate_inputs_long_title(): with pytest.raises(ValidationError): validate_inputs("x" * 201, "Detail", 100) def test_validate_inputs_file_too_large(): with pytest.raises(ValidationError): validate_inputs("Title", "Detail", 11 * 1024 * 1024) def test_normalize_time_hhmm(): t = normalize_time("09:00") assert t == time(9, 0) def test_normalize_time_hhmmss(): t = normalize_time("09:00:00") assert t == time(9, 0) def test_normalize_time_invalid(): assert normalize_time("invalid") is None # --- Excel reader tests --- 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(): form_data = { 'type_code_0': 'WORKSHOP', 'type_code_1': 'LECTURE', 'desc_0': 'Workshop description', 'desc_1': 'Lecture description', 'color_0': '#FF0000', 'color_1': '#00FF00', } 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' assert colors['LECTURE'] == 'FF00FF00' # --- Timetable tests --- def test_create_timetable(): df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': [time(9, 0)], 'Konec': [time(10, 0)], 'Program': ['Test Program'], 'Typ': ['WORKSHOP'], 'Garant': ['John Doe'], 'Poznamka': ['Test note'], }) program_descriptions = {'WORKSHOP': 'Workshop Type'} program_colors = {'WORKSHOP': 'FF0070C0'} 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(): df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': [time(9, 0)], '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" # --- Inline schedule tests --- def test_parse_inline_schedule(): 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(): form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', } with pytest.raises(ValidationError): parse_inline_schedule(form_data) def test_parse_inline_types(): 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' # --- Integration tests --- def test_inline_workflow_integration(): 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', } types_form = { 'type_name_0': 'KEYNOTE', 'type_desc_0': 'Keynote Speech', 'type_color_0': '#FF0000', } form_data = {**schedule_form, **types_form} df = parse_inline_schedule(form_data) descriptions, colors = parse_inline_types(form_data) 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" def test_excel_import_to_step2_workflow(): import base64 df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date()], 'Zacatek': ['09:00'], 'Konec': ['10:00'], 'Program': ['Opening Keynote'], 'Typ': ['KEYNOTE'], 'Garant': ['John Smith'], 'Poznamka': ['Welcome speech'] }) file_content = make_excel_bytes(df) valid_data, errors = read_excel(file_content) assert len(errors) == 0 assert len(valid_data) == 1 program_types = sorted([str(t).strip() for t in valid_data["Typ"].dropna().unique()]) assert program_types == ['KEYNOTE'] file_content_base64 = base64.b64encode(file_content).decode('utf-8') form_data = { 'title': 'Test Event', 'detail': 'Test Detail', 'file_content_base64': file_content_base64, 'type_code_0': 'KEYNOTE', 'desc_0': 'Main keynote presentation', 'color_0': '#FF0000', 'step': '3' } descriptions, colors = get_program_types(form_data) assert descriptions['KEYNOTE'] == 'Main keynote presentation' assert colors['KEYNOTE'] == 'FFFF0000' file_content_decoded = base64.b64decode(form_data['file_content_base64']) data, _ = read_excel(file_content_decoded) wb = create_timetable( data, title=form_data['title'], detail=form_data['detail'], program_descriptions=descriptions, program_colors=colors ) assert wb is not None ws = wb.active assert ws['A1'].value == 'Test Event' assert ws['A2'].value == 'Test Detail' def test_excel_import_to_inline_editor_workflow(): import base64 df = pd.DataFrame({ 'Datum': [pd.Timestamp('2025-11-13').date(), pd.Timestamp('2025-11-14').date()], 'Zacatek': ['09:00', '14:00'], 'Konec': ['10:00', '15:30'], 'Program': ['Morning Session', 'Afternoon Workshop'], 'Typ': ['KEYNOTE', 'WORKSHOP'], 'Garant': ['Alice', 'Bob'], 'Poznamka': ['', 'Hands-on'] }) file_content = make_excel_bytes(df) valid_data, errors = read_excel(file_content) assert len(errors) == 0 assert len(valid_data) == 2 file_content_base64 = base64.b64encode(file_content).decode('utf-8') file_content_decoded = base64.b64decode(file_content_base64) data_in_editor, _ = read_excel(file_content_decoded) assert len(data_in_editor) == 2 assert data_in_editor.iloc[0]['Program'] == 'Morning Session' assert data_in_editor.iloc[1]['Program'] == 'Afternoon Workshop' program_types = sorted([str(t).strip() for t in data_in_editor["Typ"].dropna().unique()]) assert set(program_types) == {'KEYNOTE', 'WORKSHOP'} # --- Inline builder tests (from test_inline_builder.py) --- def test_inline_builder_valid_form(): form_data = { 'title': 'Test Conference', 'detail': 'Testing inline builder', 'step': 'builder', 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Opening Keynote', 'typ_0': 'KEYNOTE', 'garant_0': 'Dr. Smith', 'poznamka_0': 'Welcome speech', 'datum_1': '2025-11-13', 'zacatek_1': '10:30', 'konec_1': '11:30', 'program_1': 'Workshop: Python', 'typ_1': 'WORKSHOP', 'garant_1': 'Jane Doe', 'poznamka_1': 'Hands-on coding', 'type_name_0': 'KEYNOTE', 'type_desc_0': 'Main Address', 'type_color_0': '#FF0000', 'type_name_1': 'WORKSHOP', 'type_desc_1': 'Interactive Session', 'type_color_1': '#0070C0', } schedule = parse_inline_schedule(form_data) assert len(schedule) == 2 assert schedule.iloc[0]['Program'] == 'Opening Keynote' descriptions, colors = parse_inline_types(form_data) assert len(descriptions) == 2 assert descriptions['KEYNOTE'] == 'Main Address' assert colors['KEYNOTE'] == 'FFFF0000' wb = create_timetable( schedule, title=form_data['title'], detail=form_data['detail'], program_descriptions=descriptions, program_colors=colors ) assert wb is not None ws = wb.active assert ws['A1'].value == 'Test Conference' def test_inline_builder_missing_type_definition(): form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Unknown Program', 'typ_0': 'UNKNOWN_TYPE', 'garant_0': 'Someone', 'poznamka_0': '', 'type_name_0': 'LECTURE', 'type_desc_0': 'Standard Lecture', 'type_color_0': '#3498db', } schedule = parse_inline_schedule(form_data) descriptions, colors = parse_inline_types(form_data) with pytest.raises(Exception): create_timetable(schedule, 'Bad', 'Bad', descriptions, colors) def test_inline_builder_empty_type_definition(): form_data = { 'type_name_0': '', 'type_desc_0': 'Empty type', 'type_color_0': '#FF0000', 'type_name_1': 'WORKSHOP', 'type_desc_1': 'Workshop Type', 'type_color_1': '#0070C0', } descriptions, colors = parse_inline_types(form_data) assert 'WORKSHOP' in descriptions assert '' not in descriptions def test_inline_builder_overlapping_times(): form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Program A', 'typ_0': 'WORKSHOP', 'garant_0': 'John', 'poznamka_0': '', 'datum_1': '2025-11-13', 'zacatek_1': '09:30', 'konec_1': '10:30', 'program_1': 'Program B', 'typ_1': 'WORKSHOP', 'garant_1': 'Jane', 'poznamka_1': '', 'type_name_0': 'WORKSHOP', 'type_desc_0': 'Workshop', 'type_color_0': '#0070C0', } schedule = parse_inline_schedule(form_data) descriptions, colors = parse_inline_types(form_data) assert len(schedule) == 2 wb = create_timetable(schedule, 'Conf', 'Test', descriptions, colors) assert wb is not None def test_inline_builder_multiday(): form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Day 1 Opening', 'typ_0': 'KEYNOTE', 'garant_0': 'Dr. A', 'poznamka_0': '', 'datum_1': '2025-11-14', 'zacatek_1': '09:00', 'konec_1': '10:00', 'program_1': 'Day 2 Opening', 'typ_1': 'KEYNOTE', 'garant_1': 'Dr. B', 'poznamka_1': '', 'type_name_0': 'KEYNOTE', 'type_desc_0': 'Keynote Speech', 'type_color_0': '#FF6600', } schedule = parse_inline_schedule(form_data) descriptions, colors = parse_inline_types(form_data) wb = create_timetable(schedule, 'Multi-day', 'Test', descriptions, colors) assert wb is not None ws = wb.active assert ws['A1'].value == 'Multi-day' def test_inline_builder_validation_errors(): form_data_missing = { 'datum_0': '2025-11-13', 'garant_0': 'John', } with pytest.raises(ValidationError): parse_inline_schedule(form_data_missing) def test_inline_builder_with_empty_rows(): form_data = { 'datum_0': '2025-11-13', 'zacatek_0': '09:00', 'konec_0': '10:00', 'program_0': 'Program 1', 'typ_0': 'WORKSHOP', 'garant_0': 'John', 'poznamka_0': '', 'datum_1': '', 'zacatek_1': '', 'konec_1': '', 'program_1': '', 'typ_1': '', 'garant_1': '', 'poznamka_1': '', 'type_name_0': 'WORKSHOP', 'type_desc_0': 'Workshop', 'type_color_0': '#0070C0', } schedule = parse_inline_schedule(form_data) assert len(schedule) == 1 assert schedule.iloc[0]['Program'] == 'Program 1'