167 lines
10 KiB
Python
167 lines
10 KiB
Python
"""
|
|
Pruebas de los lugares y traslados (lugares.py y operaciones.py): un solo
|
|
activo «Bitcoin» con sus sitios (exchanges, monederos, nodos Lightning).
|
|
|
|
python3 -m unittest discover -s pruebas -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from cartera import lugares # noqa: E402
|
|
from cartera.datos import Datos, ErrorDatos, escribir, leer # noqa: E402
|
|
from cartera.operaciones import Cartera, ErrorOperacion # noqa: E402
|
|
from cartera.vista import resumen # noqa: E402
|
|
|
|
|
|
def btc(aid="C1", nombre="Bitcoin"):
|
|
return {"id": aid, "nombre": nombre, "tipo": "Cripto", "codigo": "bitcoin", "unidad": "BTC",
|
|
"fecha_inicio": "2020-01-01", "estado": "abierta"}
|
|
|
|
|
|
class ConCartera(unittest.TestCase):
|
|
def setUp(self):
|
|
self.tmp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.tmp.cleanup)
|
|
self.carpeta = Path(self.tmp.name)
|
|
escribir(self.carpeta, Datos([btc(), btc("C2", "BTC en Phoenix")], [], [], []))
|
|
self.c = Cartera(self.carpeta)
|
|
self.addCleanup(self.c.cerrar)
|
|
self.cambium = self.c.guardar_lugar("C1", {"nombre": "Cambium", "clase": "exchange"})["id"]
|
|
self.bolsillo = self.c.guardar_lugar("C1", {"nombre": "Bolsillo", "clase": "monedero"})["id"]
|
|
|
|
def movs(self):
|
|
return self.c.datos.vivos("movimientos")
|
|
|
|
def total(self, hoy="2026-01-01"):
|
|
r = resumen(self.c.datos, dt.date.fromisoformat(hoy))
|
|
return next(f for f in r["abiertos"] if f["id"] == "C1")
|
|
|
|
|
|
class TestLugares(ConCartera):
|
|
def test_un_traslado_mueve_de_lugar_sin_cambiar_la_cartera(self):
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.01,
|
|
"importe": 200, "lugar": self.cambium})
|
|
antes = self.total()
|
|
t = self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-02", "cantidad": 0.0099,
|
|
"comision": 0.0001, "lugar": self.cambium, "lugar_destino": self.bolsillo})
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {self.bolsillo: 0.0099})
|
|
despues = self.total()
|
|
self.assertAlmostEqual(despues["cantidad"], 0.0099) # solo baja la comisión
|
|
self.assertAlmostEqual(despues["coste"], antes["coste"]) # el coste no se mueve
|
|
com = next(m for m in self.movs() if m["tipo"] == "Comision")
|
|
self.assertEqual((com["traslado"], com["lugar"]), (t["id"], self.cambium))
|
|
# editar la comisión la cambia; borrar el traslado se lleva su comisión
|
|
self.c.guardar_traslado({"id": t["id"], "activo": "C1", "fecha": "2023-01-02", "cantidad": 0.0099,
|
|
"comision": 0.0002, "lugar": self.cambium, "lugar_destino": self.bolsillo})
|
|
self.assertEqual([m["cantidad"] for m in self.movs() if m["tipo"] == "Comision"], [0.0002])
|
|
self.c.borrar_movimiento(t["id"])
|
|
self.assertEqual([m["tipo"] for m in self.movs()], ["Compra"])
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {self.cambium: 0.01})
|
|
|
|
def test_un_extremo_sin_saber_se_completa_despues(self):
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.01,
|
|
"importe": 200, "lugar": self.cambium})
|
|
t = self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-02", "cantidad": 0.01,
|
|
"lugar": self.cambium, "lugar_destino": ""})
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {"": 0.01})
|
|
self.c.completar_traslado(t["id"], lugar_destino=self.bolsillo, txid="ab" * 32)
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {self.bolsillo: 0.01})
|
|
self.c.vaciar_extremo(t["id"], "lugar_destino")
|
|
self.assertNotIn("txid", self._mov(t["id"]))
|
|
with self.assertRaisesRegex(ErrorOperacion, "mismo"):
|
|
self.c.completar_traslado(t["id"], lugar_destino=self.cambium)
|
|
|
|
def _mov(self, mid):
|
|
return next(m for m in self.movs() if m["id"] == mid)
|
|
|
|
def test_comprobaciones(self):
|
|
with self.assertRaisesRegex(ErrorOperacion, "distintos"):
|
|
self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-02", "cantidad": 1,
|
|
"lugar": self.cambium, "lugar_destino": self.cambium})
|
|
with self.assertRaisesRegex(ErrorOperacion, "no es de este activo"):
|
|
self.c.guardar_movimiento({"activo": "C2", "tipo": "Compra", "fecha": "2023-01-01",
|
|
"cantidad": 1, "importe": 1, "lugar": self.cambium})
|
|
with self.assertRaisesRegex(ErrorOperacion, "Ya hay"):
|
|
self.c.guardar_lugar("C1", {"nombre": "cambium", "clase": "exchange"})
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.01,
|
|
"importe": 200, "lugar": self.cambium})
|
|
with self.assertRaisesRegex(ErrorOperacion, "operación"):
|
|
self.c.borrar_lugar("C1", self.cambium)
|
|
self.c.poner_lugar([self.movs()[0]["id"]], self.bolsillo)
|
|
self.assertEqual(self.movs()[0]["lugar"], self.bolsillo)
|
|
|
|
def test_se_guarda_y_se_vuelve_a_leer(self):
|
|
self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-02", "cantidad": 0.01,
|
|
"lugar": self.cambium, "lugar_destino": self.bolsillo})
|
|
leidos = leer(self.carpeta)
|
|
self.assertEqual([x["nombre"] for x in leidos.activos[0]["lugares"]], ["Cambium", "Bolsillo"])
|
|
self.assertEqual(leidos.movimientos[0]["tipo"], "Traslado")
|
|
with self.assertRaises(ErrorDatos):
|
|
escribir(self.carpeta, Datos([{**btc(), "lugares": [{"id": "x", "nombre": "X", "clase": "rara"}]}]))
|
|
|
|
def test_un_traspaso_puede_decir_de_que_lugar_sale_y_a_cual_llega(self):
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.02,
|
|
"importe": 400, "lugar": self.cambium})
|
|
alla = self.c.guardar_lugar("C2", {"nombre": "Phoenix", "clase": "lightning"})["id"]
|
|
sal, ent = self.c.traspaso({"origen": "C1", "destino": "C2", "fecha": "2023-01-05", "cantidad_salida": 0.01,
|
|
"importe": 200, "lugar_origen": self.cambium, "lugar_destino": alla})
|
|
self.assertEqual((sal["lugar"], ent["lugar"]), (self.cambium, alla))
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {self.cambium: 0.01})
|
|
with self.assertRaises(ErrorOperacion): # un lugar que no es del origen
|
|
self.c.traspaso({"origen": "C1", "destino": "C2", "fecha": "2023-01-06", "cantidad_salida": 0.001,
|
|
"importe": 20, "lugar_origen": alla})
|
|
|
|
def test_convertir_un_activo_en_lugar(self):
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.02,
|
|
"importe": 400, "lugar": self.cambium})
|
|
self.c.guardar_movimiento({"activo": "C2", "tipo": "Recompensa", "fecha": "2023-02-01",
|
|
"cantidad": 0.0001})
|
|
self.c.traspaso({"origen": "C1", "destino": "C2", "fecha": "2023-01-05", "cantidad_salida": 0.01,
|
|
"cantidad_entrada": 0.0098, "importe": 200})
|
|
antes = self.total()["cantidad"] + next(f for f in resumen(self.c.datos, dt.date(2026, 1, 1))["abiertos"]
|
|
if f["id"] == "C2")["cantidad"]
|
|
lugar = self.c.convertir_en_lugar("C2", "C1", "lightning")
|
|
self.assertEqual((lugar["nombre"], lugar["clase"]), ("BTC en Phoenix", "lightning"))
|
|
self.assertNotIn("C2", [a["id"] for a in self.c.datos.vivos("activos")])
|
|
tipos = sorted(m["tipo"] for m in self.movs())
|
|
self.assertEqual(tipos, ["Comision", "Compra", "Recompensa", "Traslado"])
|
|
self.assertAlmostEqual(self.total()["cantidad"], antes)
|
|
# el traspaso viejo no decía de qué lugar salía: queda «sin lugar» para que lo pongas
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"),
|
|
{self.cambium: 0.02, lugar["id"]: 0.0099, "": -0.01})
|
|
|
|
|
|
class TestUnirLugares(ConCartera):
|
|
def test_dos_lugares_que_son_el_mismo_sitio_se_juntan(self):
|
|
viejo = self.c.guardar_lugar("C1", {"nombre": "BTC en Cambium", "clase": "exchange"})["id"]
|
|
self.c.guardar_movimiento({"activo": "C1", "tipo": "Compra", "fecha": "2023-01-01", "cantidad": 0.02,
|
|
"importe": 400, "lugar": viejo})
|
|
# Un traslado entre los dos (con comisión) y otro desde el viejo a Bolsillo
|
|
self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-02", "cantidad": 0.005, "comision": 0.0001,
|
|
"lugar": viejo, "lugar_destino": self.cambium})
|
|
self.c.guardar_traslado({"activo": "C1", "fecha": "2023-01-03", "cantidad": 0.01,
|
|
"lugar": viejo, "lugar_destino": self.bolsillo})
|
|
total = self.total()["cantidad"]
|
|
r = self.c.unir_lugares("C1", viejo, self.cambium)
|
|
self.assertEqual(r, {"movidas": 4, "traslados_quitados": 1})
|
|
self.assertNotIn(viejo, lugares.por_id(self.c.activo("C1")))
|
|
self.assertEqual(lugares.saldos(self.movs(), "C1"), {self.cambium: 0.0099, self.bolsillo: 0.01})
|
|
self.assertAlmostEqual(self.total()["cantidad"], total) # la comisión sigue contando
|
|
com = next(m for m in self.movs() if m["tipo"] == "Comision")
|
|
self.assertNotIn("traslado", com)
|
|
with self.assertRaisesRegex(ErrorOperacion, "ya no está"):
|
|
self.c.unir_lugares("C1", viejo, self.cambium)
|
|
|
|
def test_el_csv_reconoce_btc_en_binance_como_binance(self):
|
|
viejo = self.c.guardar_lugar("C1", {"nombre": "BTC en Binance", "clase": "exchange"})
|
|
self.assertEqual(lugares.buscar_exchange(self.c.activo("C1"), "Binance"), viejo)
|
|
self.assertIsNone(lugares.buscar_exchange(self.c.activo("C1"), "Bit2Me"))
|
|
self.assertEqual(lugares.buscar_exchange(self.c.activo("C1"), "cambium")["id"], self.cambium)
|