aboutsummaryrefslogtreecommitdiffstats
path: root/test_proxy.py
diff options
context:
space:
mode:
Diffstat (limited to 'test_proxy.py')
-rw-r--r--test_proxy.py119
1 files changed, 119 insertions, 0 deletions
diff --git a/test_proxy.py b/test_proxy.py
new file mode 100644
index 0000000..e1f1e51
--- /dev/null
+++ b/test_proxy.py
@@ -0,0 +1,119 @@
+#!/usr/bin/env python3
+# Copyright (C) 2026 Danilo M. <danix@danix.xyz>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+"""Self-check: run `python3 test_proxy.py`. Fakes the plugs, exercises routing and totals."""
+
+import datetime as dt
+import json
+import os
+import tempfile
+import urllib.request
+
+os.environ["HISTORY_DB"] = os.path.join(tempfile.mkdtemp(), "test.db")
+os.environ["TASMOTA_PLUGS"] = "ac=1.1.1.1,washer=2.2.2.2,dead=3.3.3.3"
+os.environ["RATE_MARGINAL"] = "0.1"
+os.environ["RATE_ALLIN"] = "0.2"
+
+import tasmota_proxy as p # noqa: E402
+
+SAMPLE = {
+ "1.1.1.1": {"Power": 1000, "Voltage": 230, "Current": 4.3,
+ "Today": 2.0, "Yesterday": 1.0, "Total": 10.0},
+ "2.2.2.2": {"Power": 500, "Voltage": 231, "Current": 2.1,
+ "Today": 1.0, "Yesterday": 0.5, "Total": 5.0},
+}
+
+
+class FakeResponse:
+ def __init__(self, host):
+ if host not in SAMPLE:
+ raise OSError("plug offline")
+ self._body = json.dumps({"StatusSNS": {"ENERGY": SAMPLE[host]}})
+
+ def read(self):
+ return self._body.encode()
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, *a):
+ return False
+
+
+p.urllib.request.urlopen = lambda url, timeout=None: FakeResponse(
+ urllib.parse.urlparse(url).hostname
+)
+
+one = p.fetch("1.1.1.1")
+assert one["power"] == 1000, one
+assert one["cost"]["today_allin"] == 0.4, one # 2.0 kWh * 0.2
+assert one["cost"]["now_hourly_marginal"] == 0.1, one # 1 kW * 0.1
+
+allp = p.fetch_all()
+assert set(allp) == {"ac", "washer", "dead"}, allp
+assert "error" in allp["dead"], allp # offline plug isolated
+assert "error" not in allp["ac"], allp
+
+t = p.total()
+assert t["power"] == 1500, t # dead plug excluded, not zero-filled
+assert t["today_kwh"] == 3.0, t
+assert t["cost"]["today_allin"] == 0.6, t
+assert t["plugs_ok"] == 2 and t["plugs_total"] == 3, t
+assert t["offline"] == ["dead"], t
+
+# --- power formatting ---
+# 1585 W -> "1.58 kW": banker's rounding on the exact half, not a typo.
+for w, want in [(1585, "1.58 kW"), (79.7, "79.7 W"), (0, "0 W"),
+ (999, "999 W"), (1000, "1.00 kW"), (2340.5, "2.34 kW")]:
+ assert p.watts(w) == want, (w, p.watts(w), want)
+assert one["power_fmt"] == "1.00 kW", one # the 1000 W fake plug
+assert p.total()["power_fmt"] == "1.50 kW", p.total()
+
+# --- paired kWh + cost rows ---
+assert p.kwh_eur(2.0) == "2 kWh · 0.40 €", p.kwh_eur(2.0) # RATE_ALLIN=0.2 here
+assert p.kwh_eur(0) == "0 kWh · 0.00 €", p.kwh_eur(0)
+t = p.total()
+assert one["today_fmt"] == "2 kWh · 0.40 €", one # per-plug rows
+assert one["total_fmt"] == "10 kWh · 2.00 €", one
+t = p.total()
+assert t["today_fmt"] == "3 kWh · 0.60 €", t # 2.0 + 1.0, dead skipped
+assert t["total_fmt"] == "15 kWh · 3.00 €", t
+
+# --- daily history ---
+yday = (dt.date.today() - dt.timedelta(days=1)).isoformat()
+
+h = p.history()
+assert h == [{"day": yday, "kwh": 1.5, "cost_allin": 0.3}], h # 1.0 + 0.5, dead skipped
+
+p.total() # poll again same day
+h = p.history()
+assert len(h) == 1 and h[0]["kwh"] == 1.5, h # idempotent, not doubled
+
+# an older day coexists rather than replacing
+with p.db() as c:
+ c.execute("INSERT OR REPLACE INTO daily VALUES (?, ?, ?)",
+ ((dt.date.today() - dt.timedelta(days=3)).isoformat(), "ac", 4.0))
+assert [r["kwh"] for r in p.history()] == [4.0, 1.5], p.history() # oldest first
+assert p.rolling(7) == 5.5, p.rolling(7)
+assert p.rolling(2) == 1.5, p.rolling(2) # window excludes the 3-day-old row
+
+t = p.total()
+assert t["last7_kwh"] == 5.5 and t["last7_allin"] == 1.1, t
+
+assert "<div class=bar" in p.graph_html()
+assert "5.5 kWh" in p.graph_html()
+
+print("ok")