caudal/programa/pruebas/test_instalacion.py
esetheyers cf7cc9a8c7 Caudal 0.1.2
Diseño para móvil, programa de escritorio para Linux (un solo fichero) y versiones firmadas: manifiesto de huellas, firma PGP y la clave del autor (VERIFICAR.md).
2026-10-14 16:42:53 -05:00

208 lines
9.8 KiB
Python

"""
Pruebas del programa de escritorio: dónde guarda los datos, cómo se cambian de
sitio la primera vez y la copia que hace al abrir. Se simula el instalable
(sys.frozen) con carpetas temporales; no se toca nada del equipo.
python3 -m unittest discover -s pruebas -v
"""
from __future__ import annotations
import os
import sys
import tarfile
import tempfile
import unittest
from pathlib import Path
from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from cartera import instalacion, servidor # noqa: E402
from cartera.operaciones import Cartera, ErrorOperacion # noqa: E402
class ComoInstalable(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.TemporaryDirectory()
self.addCleanup(self._tmp.cleanup)
raiz = Path(self._tmp.name)
self.programa = raiz / "descargas"
self.programa.mkdir()
self.personal = raiz / "casa" / "caudal"
for parche in (mock.patch.object(sys, "frozen", True, create=True),
mock.patch.object(sys, "executable", str(self.programa / "caudal")),
mock.patch.dict(os.environ, {"XDG_DATA_HOME": str(raiz / "casa")}),
mock.patch.object(sys, "platform", "linux")):
parche.start()
self.addCleanup(parche.stop)
os.environ.pop("APPIMAGE", None)
def abrir(self):
base, _ = instalacion.base_actual()
app = servidor.App(Cartera(base / "datos"))
self.addCleanup(lambda: app.cartera.cerrar())
return app
class TestDondeVivenLosDatos(ComoInstalable):
def test_sin_nada_van_a_la_carpeta_del_usuario(self):
self.assertEqual(instalacion.base_actual(), (self.personal, "personal"))
def test_con_carpeta_junto_al_programa_se_usa_esa(self):
(self.programa / "caudal-datos").mkdir()
self.assertEqual(instalacion.base_actual(), (self.programa / "caudal-datos", "portatil"))
def test_una_appimage_mira_junto_a_su_fichero_no_donde_se_monta(self):
otra = Path(self._tmp.name) / "usb"
(otra / "caudal-datos").mkdir(parents=True)
with mock.patch.dict(os.environ, {"APPIMAGE": str(otra / "Caudal.AppImage")}):
self.assertEqual(instalacion.base_actual(), (otra / "caudal-datos", "portatil"))
def test_desde_el_codigo_no_hay_nada_que_elegir(self):
with mock.patch.object(sys, "frozen", False, create=True):
with tempfile.TemporaryDirectory() as t:
app = servidor.App(Cartera(Path(t) / "datos"))
self.addCleanup(app.cartera.cerrar)
self.assertIsNone(app.estado()["instalacion"])
class TestElegirLaPrimeraVez(ComoInstalable):
def test_se_puede_llevar_junto_al_programa_y_volver(self):
app = self.abrir()
ins = app.estado()["instalacion"]
self.assertEqual((ins["modo"], ins["elegible"], ins["portatil_posible"]), ("personal", True, True))
ins = app.instalacion_mover("portatil")
self.assertEqual((ins["modo"], ins["carpeta"]), ("portatil", str(self.programa / "caudal-datos")))
self.assertFalse(self.personal.exists()) # no quedan dos copias
app.cartera.guardar_activo({"nombre": "Un fondo", "tipo": "Fondo", "codigo": "IE00B03HCZ61"})
self.assertTrue((self.programa / "caudal-datos" / "datos" / "activos.jsonl").is_file())
def test_con_algo_apuntado_ya_no_se_mueve(self):
app = self.abrir()
app.cartera.guardar_activo({"nombre": "Un fondo", "tipo": "Fondo", "codigo": "IE00B03HCZ61"})
self.assertFalse(app.estado()["instalacion"]["elegible"])
with self.assertRaises(ErrorOperacion):
app.instalacion_mover("portatil")
self.assertTrue((self.personal / "datos" / "activos.jsonl").is_file())
def test_no_pisa_datos_que_ya_hubiera_en_el_destino(self):
app = self.abrir()
ajena = self.programa / "caudal-datos"
with mock.patch.object(instalacion, "base_actual", return_value=(self.personal, "personal")):
(ajena / "datos").mkdir(parents=True)
(ajena / "datos" / "activos.jsonl").write_text("{}\n")
with self.assertRaises(ValueError):
instalacion.mover("portatil")
self.assertEqual((ajena / "datos" / "activos.jsonl").read_text(), "{}\n")
app.cartera.cerrar()
def test_si_la_carpeta_del_programa_no_deja_escribir_se_dice(self):
app = self.abrir()
with mock.patch.object(instalacion, "se_puede_escribir_junto_al_programa", return_value=False):
self.assertFalse(app.estado()["instalacion"]["portatil_posible"])
class TestEnElMenuDeAplicaciones(ComoInstalable):
def setUp(self):
super().setUp()
self.lanzador = self.personal.parent / "applications" / "caudal.desktop"
self.icono = self.personal.parent / "icons" / "hicolor" / "scalable" / "apps" / "caudal.svg"
def test_se_anade_y_se_quita_desde_el_programa(self):
app = self.abrir()
self.assertEqual(app.estado()["instalacion"]["lanzador"], {"puesto": False})
self.assertEqual(app.instalacion_lanzador(True)["lanzador"], {"puesto": True})
lineas = self.lanzador.read_text(encoding="utf-8").splitlines()
self.assertEqual(lineas[0], "[Desktop Entry]")
self.assertIn(f'Exec="{self.programa / "caudal"}"', lineas)
self.assertIn(f"Icon={self.icono}", lineas)
self.assertIn("Terminal=false", lineas)
self.assertIn("<svg", self.icono.read_text(encoding="utf-8"))
self.assertEqual(app.instalacion_lanzador(False)["lanzador"], {"puesto": False})
self.assertFalse(self.lanzador.exists() or self.icono.exists())
def test_la_ruta_del_programa_va_con_sus_escapes(self):
rara = self.programa / "mis $cosas 100%" / "caudal"
with mock.patch.object(sys, "executable", str(rara)):
instalacion.poner_lanzador(servidor.WEB / "icono.svg")
self.assertIn('Exec="' + str(self.programa) + '/mis \\\\$cosas 100%%/caudal"',
self.lanzador.read_text(encoding="utf-8").splitlines())
self.assertEqual(instalacion.programa_del_lanzador(), rara)
def test_si_el_programa_se_movio_se_corrige_al_arrancar(self):
icono = servidor.WEB / "icono.svg"
instalacion.poner_lanzador(icono) # apunta a un fichero que no existe
nuevo = self.programa / "otra" / "caudal"
with mock.patch.object(sys, "executable", str(nuevo)):
instalacion.repasar_lanzador(icono)
self.assertEqual(instalacion.programa_del_lanzador(), nuevo)
def test_si_la_otra_copia_sigue_ahi_no_se_le_quita_el_lanzador(self):
icono = servidor.WEB / "icono.svg"
(self.programa / "caudal").write_text("")
instalacion.poner_lanzador(icono)
with mock.patch.object(sys, "executable", str(self.programa / "otra" / "caudal")):
instalacion.repasar_lanzador(icono)
self.assertEqual(instalacion.programa_del_lanzador(), self.programa / "caudal")
def test_un_lanzador_ajeno_con_ese_nombre_no_se_borra(self):
self.lanzador.parent.mkdir(parents=True)
self.lanzador.write_text("[Desktop Entry]\nName=Otro\n", encoding="utf-8")
self.assertEqual(instalacion.lanzador(), {"puesto": False})
instalacion.quitar_lanzador()
self.assertTrue(self.lanzador.exists())
def test_fuera_de_linux_o_desde_el_codigo_no_se_ofrece(self):
app = self.abrir()
with mock.patch.object(sys, "platform", "win32"):
self.assertIsNone(instalacion.lanzador())
with self.assertRaises(ErrorOperacion):
app.instalacion_lanzador(True)
with mock.patch.object(sys, "frozen", False, create=True):
self.assertIsNone(instalacion.lanzador())
self.assertFalse(self.lanzador.exists())
class TestCopiaAlAbrir(ComoInstalable):
def test_sin_datos_no_hace_copia(self):
self.assertIsNone(instalacion.copia_al_abrir(self.personal))
def test_guarda_datos_y_ajustes_y_conserva_las_ultimas(self):
app = self.abrir()
app.cartera.guardar_activo({"nombre": "Un fondo", "tipo": "Fondo", "codigo": "IE00B03HCZ61"})
(self.personal / "bitcoin.json").write_text("{}")
(self.personal / "datos" / "adjuntos").mkdir()
(self.personal / "datos" / "adjuntos" / "grande.pdf").write_text("x")
copia = instalacion.copia_al_abrir(self.personal)
with tarfile.open(copia) as tar:
nombres = set(tar.getnames())
self.assertIn("./activos.jsonl", nombres)
self.assertIn("bitcoin.json", nombres)
self.assertFalse(any("adjuntos" in n or "cerrojo" in n for n in nombres))
with mock.patch.object(instalacion, "COPIAS", 2):
for n in range(4):
(self.personal / "backups" / f"datos-2020010{n}-000000.tar.gz").write_text("vieja")
instalacion.copia_al_abrir(self.personal)
self.assertLessEqual(len(list((self.personal / "backups").glob("datos-*.tar.gz"))), 2)
class TestYaEstaAbierto(ComoInstalable):
def test_la_marca_solo_vale_mientras_alguien_contesta(self):
import socket
self.personal.mkdir(parents=True)
self.assertIsNone(instalacion.en_marcha(self.personal))
with socket.socket() as s:
s.bind(("127.0.0.1", 0))
s.listen(1)
url = f"http://127.0.0.1:{s.getsockname()[1]}/?t=abc"
instalacion.anotar_en_marcha(self.personal, url)
self.assertEqual((self.personal / instalacion.MARCA).stat().st_mode & 0o777, 0o600)
self.assertEqual(instalacion.en_marcha(self.personal), url)
self.assertIsNone(instalacion.en_marcha(self.personal)) # se cerró sin borrar la marca
instalacion.anotar_en_marcha(self.personal, None)
self.assertFalse((self.personal / instalacion.MARCA).exists())
if __name__ == "__main__":
unittest.main()