Refactor getTotals method to clarify API contract and ensure stable response structure #1569 #1561

This commit is contained in:
Jokob @NetAlertX
2026-03-21 21:28:42 +00:00
parent fa22523a0b
commit 7278ee8cfa
2 changed files with 27 additions and 20 deletions

View File

@@ -327,20 +327,30 @@ class DeviceInstance:
return {"success": True, "inserted": row_count, "skipped_lines": skipped}
def getTotals(self):
"""Get device totals by status."""
"""Get device totals by status.
Returns a list of 6 counts in the documented positional order:
[all, connected, favorites, new, down, archived]
IMPORTANT: This order is a public API contract consumed by:
- presence.php (reads indices 0-5)
- /devices/totals/named (maps indices 0-5 to named fields)
- homepage widget datav2 (reads /devices/totals indices)
DO NOT change the order or add/remove fields without a breaking-change release.
"""
conn = get_temp_db_connection()
sql = conn.cursor()
conditions = get_device_conditions()
all_conditions = get_device_conditions()
# Build sub-selects dynamically for all dictionary entries
sub_queries = []
for key, condition in conditions.items():
# Make sure the alias is SQL-safe (no spaces or special chars)
alias = key.replace(" ", "_").lower()
sub_queries.append(f'(SELECT COUNT(*) FROM DevicesView {condition}) AS "{alias}"')
# Only the 6 public fields, in documented positional order.
# DO NOT change this order — it is a stable API contract.
keys = ["all", "connected", "favorites", "new", "down", "archived"]
sub_queries = [
f'(SELECT COUNT(*) FROM DevicesView {all_conditions[key]}) AS "{key}"'
for key in keys
]
# Join all sub-selects with commas
query = "SELECT\n " + ",\n ".join(sub_queries)
sql.execute(query)
row = sql.fetchone()

View File

@@ -8,7 +8,6 @@ import pytest
from helper import get_setting_value
from api_server.api_server_start import app
from db.db_helper import get_device_conditions
@pytest.fixture(scope="session")
@@ -163,17 +162,15 @@ def test_devices_totals(client, api_token, test_mac):
data = resp.json
assert isinstance(data, list)
# 3. Dynamically get expected length
conditions = get_device_conditions()
expected_length = len(conditions)
assert len(data) == expected_length
# 3. Verify the response has exactly 6 elements in documented order:
# [all, connected, favorites, new, down, archived]
expected_length = 6
assert len(data) == expected_length, (
f"Expected 6 totals (all, connected, favorites, new, down, archived), got {len(data)}"
)
# 4. Check that at least 1 device exists when there are any conditions
if expected_length > 0:
assert data[0] >= 1 # 'devices' count includes the dummy device
else:
# no conditions defined; data should be an empty list
assert data == []
# 4. Check that at least 1 device exists (all count includes the dummy device)
assert data[0] >= 1 # index 0 = 'all'
finally:
delete_dummy(client, api_token, test_mac)