"""Tests para upsert_xlsx_sheet.""" import os import sys from openpyxl import Workbook, load_workbook sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..")) from functions.infra.upsert_xlsx_sheet import upsert_xlsx_sheet def _build_initial_book(path): """Crea un libro con dos hojas: 'Datos' (gestionada con valor manual) y 'Personal' (trabajo del usuario que NO debe tocarse). """ wb = Workbook() wb.remove(wb.active) datos = wb.create_sheet("Datos") # Cabecera de la hoja gestionada. for c, name in enumerate(["Programa", "Clicks", "Ingreso", "EPC"], start=1): datos.cell(row=1, column=c, value=name) # Fila existente para Alpha con un valor manual en "Ingreso". datos.cell(row=2, column=1, value="Alpha") datos.cell(row=2, column=2, value=10) datos.cell(row=2, column=3, value=999) # valor manual a preservar personal = wb.create_sheet("Personal") personal.cell(row=1, column=1, value="mis_notas") personal.cell(row=2, column=1, value="no me toques") personal.cell(row=2, column=2, value=1234) wb.save(path) def test_no_destructivo_y_preserva_trabajo_manual(tmp_path): path = str(tmp_path / "libro.xlsx") _build_initial_book(path) result = upsert_xlsx_sheet( xlsx_path=path, sheet_name="Datos", records=[ {"Programa": "Alpha", "Clicks": 100, "Ingreso": 50}, {"Programa": "Beta", "Clicks": 200, "Ingreso": 80}, {"Programa": "Gamma", "Clicks": 300, "Ingreso": 120}, ], columns=["Programa", "Clicks", "Ingreso", "EPC"], key_col="Programa", preserve_cols=["Ingreso"], formulas={"EPC": {"f": '=IFERROR({Ingreso}{row}/{Clicks}{row},"")', "fmt": "0.00"}}, ) # El dict de retorno reporta lo esperado. assert result["sheet"] == "Datos" assert result["rows_written"] == 3 assert "Personal" in result["other_sheets_preserved"] assert result["manual_cells_preserved"] == 1 assert result["backup_path"] == path + ".bak" wb = load_workbook(path) # (a) La hoja "Personal" sigue existiendo con sus valores intactos. assert "Personal" in wb.sheetnames personal = wb["Personal"] assert personal.cell(row=1, column=1).value == "mis_notas" assert personal.cell(row=2, column=1).value == "no me toques" assert personal.cell(row=2, column=2).value == 1234 datos = wb["Datos"] header = {datos.cell(row=1, column=c).value: c for c in range(1, datos.max_column + 1)} # Localizar la fila de Alpha por la columna clave. alpha_row = None for r in range(2, datos.max_row + 1): if datos.cell(row=r, column=header["Programa"]).value == "Alpha": alpha_row = r break assert alpha_row is not None # (b) El valor manual en "Ingreso" para Alpha (999) NO se pisó con 50. assert datos.cell(row=alpha_row, column=header["Ingreso"]).value == 999 # (c) Se añadieron las filas nuevas (Beta y Gamma no existían antes). programas = { datos.cell(row=r, column=header["Programa"]).value for r in range(2, datos.max_row + 1) } assert {"Alpha", "Beta", "Gamma"} <= programas # (d) La columna de fórmula contiene una fórmula. epc = datos.cell(row=alpha_row, column=header["EPC"]).value assert isinstance(epc, str) and epc.startswith("=") assert "IFERROR" in epc def test_crea_libro_nuevo_si_no_existe(tmp_path): path = str(tmp_path / "nuevo.xlsx") assert not os.path.exists(path) result = upsert_xlsx_sheet( xlsx_path=path, sheet_name="Hoja1", records=[{"A": "x", "B": "y"}], columns=["A", "B"], ) assert os.path.exists(path) # Sin archivo previo no hay backup. assert result["backup_path"] == "" assert result["rows_written"] == 1 wb = load_workbook(path) assert wb.sheetnames == ["Hoja1"] ws = wb["Hoja1"] assert ws.cell(row=1, column=1).value == "A" assert ws.cell(row=2, column=1).value == "x"