"""Eksport raportu do Excela — tabela robocza (DAN-23/PRE-10).""" import io import pathlib from openpyxl import load_workbook # Eksport jest modułem odłączalnym — mieszka w app/moduly/eksport/. from app.moduly.eksport.arkusz import HEADERS, report_to_xlsx from tests.skladanie import zloz_kod APP = pathlib.Path(__file__).resolve().parents[1] / "app" # Trasy mieszkają w app/ekrany/* — patrz tests/skladanie.py. MAIN = zloz_kod() INTERPRET = (APP / "templates" / "interpret.html").read_text(encoding="utf-8") def _report() -> dict: return {"engine": "own", "objects": [ {"name": "Sun", "facets": [ {"type": "sign", "label": "w znaku Cancer", "token": "[Su + Can", "samples": [ {"significator": "Su Can", "expanded": "Słońce w Raku", "effect": "opiekuńczy"}, {"significator": "Su Can h10", "expanded": "Słońce w Raku, 10 dom", "effect": "kariera 50% publiczna"}]}, {"type": "house", "label": "w 10 domu", "token": "10 H.", "samples": [ {"significator": "Su h10", "expanded": "Słońce w 10 domu", "effect": "ambicja"}]}]}, {"name": "Moon", "facets": [ {"type": "aspect", "label": "trygon z Mars", "token": "[Mo tri [Ma", "samples": [ {"significator": "Mo tri Ma", "expanded": "Księżyc trygon Mars", "effect": "energia"}]}]}, ]} def _load(report): return load_workbook(io.BytesIO(report_to_xlsx(report))).active def test_header_row_matches_columns(): ws = _load(_report()) assert [c.value for c in ws[1]] == HEADERS def test_one_row_per_sample_flattened(): """Zagnieżdżenie obiekt→fasety→próbki rozwijamy na płaskie wiersze.""" ws = _load(_report()) assert ws.max_row == 1 + 4 # nagłówek + 4 dopasowania def test_row_carries_object_facet_and_effect(): ws = _load(_report()) vals = [[c.value for c in row] for row in ws.iter_rows(min_row=2)] first = vals[0] assert first[0] == "Sun" and first[1] == "w znaku Cancer" and first[2] == "sign" assert first[6] == "opiekuńczy" # kolumna „Opis / efekt" assert any("kariera 50% publiczna" in (r[6] or "") for r in vals) # % nie psuje niczego def test_autofilter_and_frozen_header_for_working_table(): """Filtrowanie/sortowanie od razu w Excelu (PRE-10).""" ws = _load(_report()) assert ws.auto_filter.ref == "A1:G5" assert ws.freeze_panes == "A2" def test_empty_report_is_safe(): ws = _load({"objects": []}) assert [c.value for c in ws[1]] == HEADERS assert ws.max_row == 1 # sam nagłówek, bez wyjątku assert ws.auto_filter.ref == "A1:G1" # ── wpięcie w UI ── def test_interpret_has_export_button(): assert 'name="action" value="export"' in INTERPRET def test_handler_returns_xlsx_download(): # Eksport jest modułem odłączalnym, więc akcję zgłasza on sam — ekran nie # wymienia jej z nazwy. Pilnujemy zgłoszenia, nie dawnego `if` w handlerze. assert '"export": "export"' in MAIN assert "report_to_xlsx" in MAIN assert "spreadsheetml.sheet" in MAIN # media type .xlsx assert 'filename="interpretacje.xlsx"' in MAIN def test_the_export_action_really_returns_a_spreadsheet(tmp_path, monkeypatch): """Ścieżka szczęśliwa, nie tylko odmowa. Testy uprawnień sprawdzały dotąd, że konto BEZ uprawnienia arkusza nie dostanie. Po wydzieleniu eksportu do modułu okazało się, że nikt nie sprawdza, czy konto Z uprawnieniem dostaje cokolwiek — a moduł dostawał zależności dopiero z wywołania montującego i brak jednej z nich wyszedłby dopiero przy pierwszym kliknięciu. """ import os monkeypatch.setenv("ACCOUNTS_FILE", str(tmp_path / "accounts.json")) monkeypatch.setenv("APP_USER", "szef") monkeypatch.setenv("APP_PASSWORD", "tajne-szefa") monkeypatch.delenv("APP_USERS", raising=False) monkeypatch.setenv("RATE_LIMIT_PER_MIN", "0") monkeypatch.setenv("SESSION_SECRET", "t" * 64) monkeypatch.setenv("COOKIES_INSECURE", "1") os.environ.setdefault("SESSION_SECRET", "t" * 64) from starlette.testclient import TestClient from app import security, session from app.main import app, logic monkeypatch.setattr(logic, "report", lambda **kw: {"objects": [], "engine": "test"}) c = TestClient(app, follow_redirects=False) naglowki = {"Cookie": f"{session.COOKIE}={security.issue_session('szef')}"} r = c.post("/interpret", headers=naglowki, data={"date": "1984-04-30", "time": "11:20", "tz_offset": "2", "lat": "50.06", "lon": "19.94", "action": "export"}) assert r.status_code == 200 assert "spreadsheetml" in r.headers.get("content-type", "") assert r.headers.get("content-disposition", "").endswith('"interpretacje.xlsx"') assert r.content[:2] == b"PK", "arkusz .xlsx to archiwum ZIP — to nim nie jest"