mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
98
test/api_endpoints/test_dbquery_endpoints.py
Normal file
98
test/api_endpoints/test_dbquery_endpoints.py
Normal file
@@ -0,0 +1,98 @@
|
||||
import sys
|
||||
import base64
|
||||
import random
|
||||
import os
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from utils.datetime_utils import timeNowDB
|
||||
from api_server.api_server_start import app
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def b64(sql: str) -> str:
|
||||
"""Helper to base64 encode SQL"""
|
||||
return base64.b64encode(sql.encode("utf-8")).decode("utf-8")
|
||||
|
||||
|
||||
# -----------------------------
|
||||
# Device lifecycle via dbquery endpoints
|
||||
# -----------------------------
|
||||
def test_dbquery_create_device(client, api_token, test_mac):
|
||||
|
||||
now = timeNowDB()
|
||||
|
||||
sql = f"""
|
||||
INSERT INTO Devices (devMac, devName, devVendor, devOwner, devFirstConnection, devLastConnection, devLastIP)
|
||||
VALUES ('{test_mac}', 'UnitTestDevice', 'TestVendor', 'UnitTest', '{now}', '{now}', '192.168.100.22' )
|
||||
"""
|
||||
resp = client.post("/dbquery/write", json={"rawSql": b64(sql)}, headers=auth_headers(api_token))
|
||||
print(resp.json)
|
||||
print(resp)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
assert resp.json.get("affected_rows") == 1
|
||||
|
||||
|
||||
def test_dbquery_read_device(client, api_token, test_mac):
|
||||
sql = f"SELECT * FROM Devices WHERE devMac = '{test_mac}'"
|
||||
resp = client.post("/dbquery/read", json={"rawSql": b64(sql)}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
results = resp.json.get("results")
|
||||
assert any(row["devMac"] == test_mac for row in results)
|
||||
|
||||
|
||||
def test_dbquery_update_device(client, api_token, test_mac):
|
||||
sql = f"""
|
||||
UPDATE Devices
|
||||
SET devName = 'UnitTestDeviceRenamed'
|
||||
WHERE devMac = '{test_mac}'
|
||||
"""
|
||||
resp = client.post("/dbquery/write", json={"rawSql": b64(sql)}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
assert resp.json.get("affected_rows") == 1
|
||||
|
||||
# Verify update
|
||||
sql_check = f"SELECT devName FROM Devices WHERE devMac = '{test_mac}'"
|
||||
resp2 = client.post("/dbquery/read", json={"rawSql": b64(sql_check)}, headers=auth_headers(api_token))
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json.get("results")[0]["devName"] == "UnitTestDeviceRenamed"
|
||||
|
||||
|
||||
def test_dbquery_delete_device(client, api_token, test_mac):
|
||||
sql = f"DELETE FROM Devices WHERE devMac = '{test_mac}'"
|
||||
resp = client.post("/dbquery/write", json={"rawSql": b64(sql)}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
assert resp.json.get("affected_rows") == 1
|
||||
|
||||
# Verify deletion
|
||||
sql_check = f"SELECT * FROM Devices WHERE devMac = '{test_mac}'"
|
||||
resp2 = client.post("/dbquery/read", json={"rawSql": b64(sql_check)}, headers=auth_headers(api_token))
|
||||
assert resp2.status_code == 200
|
||||
assert resp2.json.get("results") == []
|
||||
153
test/api_endpoints/test_device_endpoints.py
Normal file
153
test/api_endpoints/test_device_endpoints.py
Normal file
@@ -0,0 +1,153 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def test_create_device(client, api_token, test_mac):
|
||||
payload = {
|
||||
"createNew": True,
|
||||
"devType": "Test Device",
|
||||
"devOwner": "Unit Test",
|
||||
"devType": "Router",
|
||||
"devVendor": "TestVendor",
|
||||
}
|
||||
resp = client.post(
|
||||
f"/device/{test_mac}", json=payload, headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
def test_get_device(client, api_token, test_mac):
|
||||
# First create it
|
||||
client.post(
|
||||
f"/device/{test_mac}", json={"createNew": True}, headers=auth_headers(api_token)
|
||||
)
|
||||
# Then retrieve it
|
||||
resp = client.get(f"/device/{test_mac}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("devMac") == test_mac
|
||||
|
||||
|
||||
def test_reset_device_props(client, api_token, test_mac):
|
||||
client.post(
|
||||
f"/device/{test_mac}", json={"createNew": True}, headers=auth_headers(api_token)
|
||||
)
|
||||
resp = client.post(
|
||||
f"/device/{test_mac}/reset-props", json={}, headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
def test_delete_device_events(client, api_token, test_mac):
|
||||
client.post(
|
||||
f"/device/{test_mac}", json={"createNew": True}, headers=auth_headers(api_token)
|
||||
)
|
||||
resp = client.delete(
|
||||
f"/device/{test_mac}/events/delete", headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
def test_delete_device(client, api_token, test_mac):
|
||||
client.post(
|
||||
f"/device/{test_mac}", json={"createNew": True}, headers=auth_headers(api_token)
|
||||
)
|
||||
resp = client.delete(f"/device/{test_mac}/delete", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
def test_copy_device(client, api_token, test_mac):
|
||||
# Step 1: Create the source device
|
||||
payload = {"createNew": True}
|
||||
resp = client.post(
|
||||
f"/device/{test_mac}", json=payload, headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# Step 2: Generate a target MAC
|
||||
target_mac = "AA:BB:CC:" + ":".join(
|
||||
f"{random.randint(0,255):02X}" for _ in range(3)
|
||||
)
|
||||
|
||||
# Step 3: Copy device
|
||||
copy_payload = {"macFrom": test_mac, "macTo": target_mac}
|
||||
resp = client.post(
|
||||
"/device/copy", json=copy_payload, headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# Step 4: Verify new device exists
|
||||
resp = client.get(f"/device/{target_mac}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("devMac") == target_mac
|
||||
|
||||
# Cleanup: delete both devices
|
||||
client.delete(f"/device/{test_mac}/delete", headers=auth_headers(api_token))
|
||||
client.delete(f"/device/{target_mac}/delete", headers=auth_headers(api_token))
|
||||
|
||||
|
||||
def test_update_device_column(client, api_token, test_mac):
|
||||
# First, create the device
|
||||
client.post(
|
||||
f"/device/{test_mac}",
|
||||
json={"createNew": True},
|
||||
headers=auth_headers(api_token),
|
||||
)
|
||||
|
||||
# Update its parent MAC
|
||||
resp = client.post(
|
||||
f"/device/{test_mac}/update-column",
|
||||
json={"columnName": "devParentMAC", "columnValue": "Internet"},
|
||||
headers=auth_headers(api_token),
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# Try updating a non-existent device
|
||||
resp_missing = client.post(
|
||||
"/device/11:22:33:44:55:66/update-column",
|
||||
json={"columnName": "devParentMAC", "columnValue": "Internet"},
|
||||
headers=auth_headers(api_token),
|
||||
)
|
||||
|
||||
assert resp_missing.status_code == 404
|
||||
assert resp_missing.json.get("success") is False
|
||||
197
test/api_endpoints/test_devices_endpoints.py
Normal file
197
test/api_endpoints/test_devices_endpoints.py
Normal file
@@ -0,0 +1,197 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def create_dummy(client, api_token, test_mac):
|
||||
payload = {
|
||||
"createNew": True,
|
||||
"devName": "Test Device",
|
||||
"devOwner": "Unit Test",
|
||||
"devType": "Router",
|
||||
"devVendor": "TestVendor",
|
||||
}
|
||||
resp = client.post(f"/device/{test_mac}", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
def test_get_all_devices(client, api_token, test_mac):
|
||||
# Ensure there is at least one device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# Fetch all devices
|
||||
resp = client.get("/devices", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
devices = resp.json.get("devices")
|
||||
assert isinstance(devices, list)
|
||||
# Ensure our test device is in the list
|
||||
assert any(d["devMac"] == test_mac for d in devices)
|
||||
|
||||
|
||||
def test_delete_devices_with_macs(client, api_token, test_mac):
|
||||
# First create device so it exists
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
client.post(f"/device/{test_mac}", json={"createNew": True}, headers=auth_headers(api_token))
|
||||
|
||||
# Delete by MAC
|
||||
resp = client.delete("/devices", json={"macs": [test_mac]}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_delete_all_empty_macs(client, api_token):
|
||||
resp = client.delete("/devices/empty-macs", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
# Expect success flag in response
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
def test_delete_unknown_devices(client, api_token):
|
||||
resp = client.delete("/devices/unknown", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_export_devices_csv(client, api_token, test_mac):
|
||||
# Create a device first
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# Export devices as CSV
|
||||
resp = client.get("/devices/export/csv", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.mimetype == "text/csv"
|
||||
assert "attachment; filename=devices.csv" in resp.headers.get("Content-disposition", "")
|
||||
|
||||
# CSV should contain test_mac
|
||||
assert test_mac in resp.data.decode()
|
||||
|
||||
def test_export_devices_json(client, api_token, test_mac):
|
||||
# Create a device first
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# Export devices as JSON
|
||||
resp = client.get("/devices/export/json", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.is_json
|
||||
data = resp.get_json()
|
||||
assert any(dev.get("devMac") == test_mac for dev in data["data"])
|
||||
|
||||
|
||||
def test_export_devices_invalid_format(client, api_token):
|
||||
# Request with unsupported format
|
||||
resp = client.get("/devices/export/invalid", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 400
|
||||
assert "Unsupported format" in resp.json.get("error")
|
||||
|
||||
|
||||
def test_export_import_cycle_base64(client, api_token, test_mac):
|
||||
# 1. Create a dummy device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# 2. Export devices as CSV
|
||||
resp = client.get("/devices/export/csv", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
csv_data = resp.data.decode("utf-8")
|
||||
|
||||
print(csv_data)
|
||||
|
||||
# Ensure our dummy device is in the CSV
|
||||
assert test_mac in csv_data
|
||||
assert "Test Device" in csv_data
|
||||
|
||||
# 3. Base64-encode the CSV for JSON payload
|
||||
csv_base64 = base64.b64encode(csv_data.encode("utf-8")).decode("utf-8")
|
||||
json_payload = {"content": csv_base64}
|
||||
|
||||
# 4. POST to import endpoint with JSON content
|
||||
resp = client.post(
|
||||
"/devices/import",
|
||||
json=json_payload,
|
||||
headers={**auth_headers(api_token), "Content-Type": "application/json"}
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# 5. Verify import results
|
||||
assert resp.json.get("inserted") >= 1
|
||||
assert resp.json.get("skipped_lines") == []
|
||||
|
||||
def test_devices_totals(client, api_token, test_mac):
|
||||
# 1. Create a dummy device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# 2. Call the totals endpoint
|
||||
resp = client.get("/devices/totals", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
|
||||
# 3. Ensure the response is a JSON list
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
assert len(data) == 6 # devices, connected, favorites, new, down, archived
|
||||
|
||||
# 4. Check that at least 1 device exists
|
||||
assert data[0] >= 1 # 'devices' count includes the dummy device
|
||||
|
||||
|
||||
def test_devices_by_status(client, api_token, test_mac):
|
||||
# 1. Create a dummy device
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# 2. Request devices by a valid status
|
||||
resp = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
assert any(d["id"] == test_mac for d in data)
|
||||
|
||||
# 3. Request devices with an invalid/unknown status
|
||||
resp_invalid = client.get("/devices/by-status?status=invalid_status", headers=auth_headers(api_token))
|
||||
assert resp_invalid.status_code == 200
|
||||
# Should return empty list for unknown status
|
||||
assert resp_invalid.json == []
|
||||
|
||||
# 4. Check favorite formatting if devFavorite = 1
|
||||
# Update dummy device to favorite
|
||||
client.post(
|
||||
f"/device/{test_mac}",
|
||||
json={"devFavorite": 1},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
resp_fav = client.get("/devices/by-status?status=my", headers=auth_headers(api_token))
|
||||
fav_data = next((d for d in resp_fav.json if d["id"] == test_mac), None)
|
||||
assert fav_data is not None
|
||||
assert "★" in fav_data["title"]
|
||||
|
||||
def test_delete_test_devices(client, api_token, test_mac):
|
||||
|
||||
# Delete by MAC
|
||||
resp = client.delete("/devices", json={"macs": ["AA:BB:CC:*"]}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
150
test/api_endpoints/test_events_endpoints.py
Normal file
150
test/api_endpoints/test_events_endpoints.py
Normal file
@@ -0,0 +1,150 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from utils.datetime_utils import timeNowTZ
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
def create_event(client, api_token, mac, event="UnitTest Event", days_old=None):
|
||||
payload = {"ip": "0.0.0.0", "event_type": event}
|
||||
|
||||
# Calculate the event_time if days_old is given
|
||||
if days_old is not None:
|
||||
event_time = timeNowTZ() - timedelta(days=days_old)
|
||||
# ISO 8601 string
|
||||
payload["event_time"] = event_time.isoformat()
|
||||
|
||||
return client.post(f"/events/create/{mac}", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
def list_events(client, api_token, mac=None):
|
||||
url = "/events" if mac is None else f"/events?mac={mac}"
|
||||
return client.get(url, headers=auth_headers(api_token))
|
||||
|
||||
def test_create_event(client, api_token, test_mac):
|
||||
# create event
|
||||
resp = create_event(client, api_token, test_mac)
|
||||
assert resp.status_code == 200
|
||||
data = resp.get_json()
|
||||
assert data.get("success") is True
|
||||
|
||||
# confirm event exists
|
||||
resp = list_events(client, api_token, test_mac)
|
||||
assert resp.status_code == 200
|
||||
events = resp.get_json().get("events", [])
|
||||
assert any(ev.get("eve_MAC") == test_mac for ev in events)
|
||||
|
||||
|
||||
def test_delete_events_for_mac(client, api_token, test_mac):
|
||||
# create event
|
||||
resp = create_event(client, api_token, test_mac)
|
||||
assert resp.status_code == 200
|
||||
|
||||
# confirm exists
|
||||
resp = list_events(client, api_token, test_mac)
|
||||
assert resp.status_code == 200
|
||||
events = resp.json.get("events", [])
|
||||
assert any(ev["eve_MAC"] == test_mac for ev in events)
|
||||
|
||||
# delete
|
||||
resp = client.delete(f"/events/{test_mac}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# confirm deleted
|
||||
resp = list_events(client, api_token, test_mac)
|
||||
assert resp.status_code == 200
|
||||
assert len(resp.json.get("events", [])) == 0
|
||||
|
||||
def test_get_events_totals(client, api_token):
|
||||
# 1. Request totals with default period
|
||||
resp = client.get(
|
||||
"/sessions/totals",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
data = resp.json
|
||||
assert isinstance(data, list)
|
||||
# Expecting 6 counts: all_events, sessions, missing, voided, new, down
|
||||
assert len(data) == 6
|
||||
for count in data:
|
||||
assert isinstance(count, int) # each should be a number
|
||||
|
||||
# 2. Request totals with custom period
|
||||
resp_month = client.get(
|
||||
"/sessions/totals?period=1 month",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp_month.status_code == 200
|
||||
data_month = resp_month.json
|
||||
assert isinstance(data_month, list)
|
||||
assert len(data_month) == 6
|
||||
|
||||
|
||||
|
||||
def test_delete_all_events(client, api_token, test_mac):
|
||||
# create two events
|
||||
create_event(client, api_token, test_mac)
|
||||
create_event(client, api_token, "FF:FF:FF:FF:FF:FF")
|
||||
|
||||
resp = list_events(client, api_token)
|
||||
assert len(resp.json) >= 2
|
||||
|
||||
# delete all
|
||||
resp = client.delete("/events", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# confirm no events
|
||||
resp = list_events(client, api_token)
|
||||
assert len(resp.json.get("events", [])) == 0
|
||||
|
||||
|
||||
def test_delete_events_dynamic_days(client, api_token, test_mac):
|
||||
# create old + new events
|
||||
create_event(client, api_token, test_mac, days_old=40) # should be deleted
|
||||
create_event(client, api_token, test_mac, days_old=5) # should remain
|
||||
|
||||
resp = list_events(client, api_token, test_mac)
|
||||
assert len(resp.json) == 2
|
||||
|
||||
# delete events older than 30 days
|
||||
resp = client.delete("/events/30", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
assert "Deleted events older than 30 days" in resp.json.get("message", "")
|
||||
|
||||
# confirm only recent remains
|
||||
resp = list_events(client, api_token, test_mac)
|
||||
events = resp.get_json().get("events", [])
|
||||
mac_events = [ev for ev in events if ev.get("eve_MAC") == test_mac]
|
||||
assert len(mac_events) == 1
|
||||
|
||||
|
||||
170
test/api_endpoints/test_graphq_endpoints.py
Normal file
170
test/api_endpoints/test_graphq_endpoints.py
Normal file
@@ -0,0 +1,170 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def test_graphql_debug_get(client):
|
||||
"""GET /graphql should return the debug string"""
|
||||
resp = client.get("/graphql")
|
||||
assert resp.status_code == 200
|
||||
assert resp.data.decode() == "NetAlertX GraphQL server running."
|
||||
|
||||
def test_graphql_post_unauthorized(client):
|
||||
"""POST /graphql without token should return 401"""
|
||||
query = {"query": "{ devices { devName devMac } }"}
|
||||
resp = client.post("/graphql", json=query)
|
||||
assert resp.status_code == 401
|
||||
assert "Unauthorized access attempt" in resp.json.get("error", "")
|
||||
|
||||
# --- DEVICES TESTS ---
|
||||
|
||||
def test_graphql_post_devices(client, api_token):
|
||||
"""POST /graphql with a valid token should return device data"""
|
||||
query = {
|
||||
"query": """
|
||||
{
|
||||
devices {
|
||||
devices {
|
||||
devGUID
|
||||
devGroup
|
||||
devIsRandomMac
|
||||
devParentChildrenCount
|
||||
}
|
||||
count
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
resp = client.post("/graphql", json=query, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
|
||||
body = resp.get_json()
|
||||
|
||||
# GraphQL spec: response always under "data"
|
||||
assert "data" in body
|
||||
data = body["data"]
|
||||
|
||||
assert "devices" in data
|
||||
assert isinstance(data["devices"]["devices"], list)
|
||||
assert isinstance(data["devices"]["count"], int)
|
||||
|
||||
# --- SETTINGS TESTS ---
|
||||
|
||||
def test_graphql_post_settings(client, api_token):
|
||||
"""POST /graphql should return settings data"""
|
||||
query = {
|
||||
"query": """
|
||||
{
|
||||
settings {
|
||||
settings { setKey setValue setGroup }
|
||||
count
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
resp = client.post("/graphql", json=query, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json.get("data", {})
|
||||
assert "settings" in data
|
||||
assert isinstance(data["settings"]["settings"], list)
|
||||
|
||||
# --- LANGSTRINGS TESTS ---
|
||||
|
||||
def test_graphql_post_langstrings_specific(client, api_token):
|
||||
"""Retrieve a specific langString in a given language"""
|
||||
query = {
|
||||
"query": """
|
||||
{
|
||||
langStrings(langCode: "en_us", langStringKey: "settings_other_scanners") {
|
||||
langStrings { langCode langStringKey langStringText }
|
||||
count
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
resp = client.post("/graphql", json=query, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json.get("data", {}).get("langStrings", {})
|
||||
assert data["count"] >= 1
|
||||
for entry in data["langStrings"]:
|
||||
assert entry["langCode"] == "en_us"
|
||||
assert entry["langStringKey"] == "settings_other_scanners"
|
||||
assert isinstance(entry["langStringText"], str)
|
||||
|
||||
|
||||
def test_graphql_post_langstrings_fallback(client, api_token):
|
||||
"""Fallback to en_us if requested language string is empty"""
|
||||
query = {
|
||||
"query": """
|
||||
{
|
||||
langStrings(langCode: "de_de", langStringKey: "settings_other_scanners") {
|
||||
langStrings { langCode langStringKey langStringText }
|
||||
count
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
resp = client.post("/graphql", json=query, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json.get("data", {}).get("langStrings", {})
|
||||
assert data["count"] >= 1
|
||||
# Ensure fallback occurred if de_de text is empty
|
||||
for entry in data["langStrings"]:
|
||||
assert entry["langStringText"] != ""
|
||||
|
||||
|
||||
def test_graphql_post_langstrings_all_languages(client, api_token):
|
||||
"""Retrieve all languages for a given key"""
|
||||
query = {
|
||||
"query": """
|
||||
{
|
||||
enStrings: langStrings(langCode: "en_us", langStringKey: "settings_other_scanners") {
|
||||
langStrings { langCode langStringKey langStringText }
|
||||
count
|
||||
}
|
||||
deStrings: langStrings(langCode: "de_de", langStringKey: "settings_other_scanners") {
|
||||
langStrings { langCode langStringKey langStringText }
|
||||
count
|
||||
}
|
||||
}
|
||||
"""
|
||||
}
|
||||
resp = client.post("/graphql", json=query, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
data = resp.json.get("data", {})
|
||||
assert "enStrings" in data
|
||||
assert "deStrings" in data
|
||||
# At least one string in each language
|
||||
assert data["enStrings"]["count"] >= 1
|
||||
assert data["deStrings"]["count"] >= 1
|
||||
# Ensure langCode matches
|
||||
assert all(e["langCode"] == "en_us" for e in data["enStrings"]["langStrings"])
|
||||
assert all(e["langCode"] == "de_de" for e in data["deStrings"]["langStrings"])
|
||||
41
test/api_endpoints/test_history_endpoints.py
Normal file
41
test/api_endpoints/test_history_endpoints.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = os.getenv("NETALERTX_APP", "/app")
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def test_delete_history(client, api_token):
|
||||
resp = client.delete(f"/history", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
61
test/api_endpoints/test_logs_endpoints.py
Normal file
61
test/api_endpoints/test_logs_endpoints.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import sys
|
||||
import random
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = "/app"
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
# ----------------------------
|
||||
# Fixtures
|
||||
# ----------------------------
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
# ----------------------------
|
||||
# Logs Endpoint Tests
|
||||
# ----------------------------
|
||||
def test_clean_log(client, api_token):
|
||||
resp = client.delete("/logs?file=app.log", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_clean_log_not_allowed(client, api_token):
|
||||
resp = client.delete("/logs?file=not_allowed.log", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 400
|
||||
assert resp.json.get("success") is False
|
||||
|
||||
# ----------------------------
|
||||
# Execution Queue Endpoint Tests
|
||||
# ----------------------------
|
||||
def test_add_to_execution_queue(client, api_token):
|
||||
action_name = f"test_action_{random.randint(0,9999)}"
|
||||
resp = client.post(
|
||||
"/logs/add-to-execution-queue",
|
||||
json={"action": action_name},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
assert action_name in resp.json.get("message", "")
|
||||
|
||||
def test_add_to_execution_queue_missing_action(client, api_token):
|
||||
resp = client.post(
|
||||
"/logs/add-to-execution-queue",
|
||||
json={},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 400
|
||||
assert resp.json.get("success") is False
|
||||
assert "Missing required 'action'" in resp.json.get("error", "")
|
||||
111
test/api_endpoints/test_messaging_in_app_endpoints.py
Normal file
111
test/api_endpoints/test_messaging_in_app_endpoints.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# -----------------------------
|
||||
# In-app notifications tests with cleanup
|
||||
# -----------------------------
|
||||
|
||||
import json
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import pytest
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Define the installation path and extend the system path for plugin imports
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from api_server.api_server_start import app
|
||||
from messaging.in_app import NOTIFICATION_API_FILE # Import the path to notifications file
|
||||
from helper import get_setting_value
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
@pytest.fixture
|
||||
def random_content():
|
||||
return "Test Notification " + "".join(random.choices(string.ascii_letters + string.digits, k=6))
|
||||
|
||||
@pytest.fixture
|
||||
def notification_guid(client, api_token, random_content):
|
||||
# Write a notification and return its GUID
|
||||
resp = client.post(
|
||||
"/messaging/in-app/write",
|
||||
json={"content": random_content, "level": "alert"},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
# Fetch the unread notifications and get GUID
|
||||
resp = client.get("/messaging/in-app/unread", headers=auth_headers(api_token))
|
||||
data = resp.json
|
||||
guid = next((n["guid"] for n in data if n["content"] == random_content), None)
|
||||
assert guid is not None
|
||||
return guid
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def cleanup_notifications():
|
||||
# Runs before and after each test
|
||||
# Backup original file if exists
|
||||
backup = None
|
||||
if os.path.exists(NOTIFICATION_API_FILE):
|
||||
with open(NOTIFICATION_API_FILE, "r") as f:
|
||||
backup = f.read()
|
||||
|
||||
yield # run the test
|
||||
|
||||
# Cleanup after test
|
||||
with open(NOTIFICATION_API_FILE, "w") as f:
|
||||
f.write("[]")
|
||||
|
||||
# Restore backup if needed
|
||||
if backup:
|
||||
with open(NOTIFICATION_API_FILE, "w") as f:
|
||||
f.write(backup)
|
||||
|
||||
# -----------------------------
|
||||
def test_write_notification(client, api_token, random_content):
|
||||
resp = client.post(
|
||||
"/messaging/in-app/write",
|
||||
json={"content": random_content, "level": "alert"},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_get_unread_notifications(client, api_token, random_content):
|
||||
client.post("/messaging/in-app/write", json={"content": random_content}, headers=auth_headers(api_token))
|
||||
resp = client.get("/messaging/in-app/unread", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
notifications = resp.json
|
||||
assert any(n["content"] == random_content for n in notifications)
|
||||
|
||||
def test_mark_all_notifications_read(client, api_token, random_content):
|
||||
client.post("/messaging/in-app/write", json={"content": random_content}, headers=auth_headers(api_token))
|
||||
resp = client.post("/messaging/in-app/read/all", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_mark_single_notification_read(client, api_token, notification_guid):
|
||||
resp = client.post(f"/messaging/in-app/read/{notification_guid}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_delete_single_notification(client, api_token, notification_guid):
|
||||
resp = client.delete(f"/messaging/in-app/delete/{notification_guid}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
def test_delete_all_notifications(client, api_token, random_content):
|
||||
# Add a notification first
|
||||
client.post("/messaging/in-app/write", json={"content": random_content}, headers=auth_headers(api_token))
|
||||
resp = client.delete("/messaging/in-app/delete", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
204
test/api_endpoints/test_nettools_endpoints.py
Normal file
204
test/api_endpoints/test_nettools_endpoints.py
Normal file
@@ -0,0 +1,204 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import base64
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
|
||||
def create_dummy(client, api_token, test_mac):
|
||||
payload = {
|
||||
"createNew": True,
|
||||
"devName": "Test Device",
|
||||
"devOwner": "Unit Test",
|
||||
"devType": "Router",
|
||||
"devVendor": "TestVendor",
|
||||
}
|
||||
resp = client.post(f"/device/{test_mac}", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
def test_wakeonlan_device(client, api_token, test_mac):
|
||||
# 1. Ensure at least one device exists
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# 2. Fetch all devices
|
||||
resp = client.get("/devices", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
devices = resp.json.get("devices", [])
|
||||
assert len(devices) > 0
|
||||
|
||||
# 3. Pick the first device (or the test device)
|
||||
device_mac = devices[0]["devMac"]
|
||||
|
||||
# 4. Call the wakeonlan endpoint
|
||||
resp = client.post(
|
||||
"/nettools/wakeonlan",
|
||||
json={"devMac": device_mac},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
|
||||
# 5. Conditional assertions based on MAC
|
||||
if device_mac.lower() == 'internet' or device_mac == test_mac:
|
||||
# For athe dummy "internet" or test MAC, expect a 400 response
|
||||
assert resp.status_code == 400
|
||||
else:
|
||||
# For any other MAC, expect a 200 response
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert data.get("success") is True
|
||||
assert "WOL packet sent" in data.get("message", "")
|
||||
|
||||
def test_speedtest_endpoint(client, api_token):
|
||||
# 1. Call the speedtest endpoint
|
||||
resp = client.get("/nettools/speedtest", headers=auth_headers(api_token))
|
||||
|
||||
# 2. Assertions
|
||||
if resp.status_code == 403:
|
||||
# Unauthorized access
|
||||
data = resp.json
|
||||
assert "error" in data
|
||||
assert data["error"] == "Forbidden"
|
||||
else:
|
||||
# Expect success
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert data.get("success") is True
|
||||
assert "output" in data
|
||||
assert isinstance(data["output"], list)
|
||||
# Optionally check that output lines are strings
|
||||
assert all(isinstance(line, str) for line in data["output"])
|
||||
|
||||
def test_traceroute_device(client, api_token, test_mac):
|
||||
# 1. Ensure at least one device exists
|
||||
create_dummy(client, api_token, test_mac)
|
||||
|
||||
# 2. Fetch all devices
|
||||
resp = client.get("/devices", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
devices = resp.json.get("devices", [])
|
||||
assert len(devices) > 0
|
||||
|
||||
# 3. Pick the first device
|
||||
device_ip = devices[0].get("devLastIP", "192.168.1.1") # fallback if dummy has no IP
|
||||
|
||||
# 4. Call the traceroute endpoint
|
||||
resp = client.post(
|
||||
"/nettools/traceroute",
|
||||
json={"devLastIP": device_ip},
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
|
||||
# 5. Assertions
|
||||
if not device_ip or device_ip.lower() == 'invalid':
|
||||
# Expect 400 if IP is missing or invalid
|
||||
assert resp.status_code == 400
|
||||
data = resp.json
|
||||
assert data.get("success") is False
|
||||
else:
|
||||
# Expect 200 and valid traceroute output
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert data.get("success") is True
|
||||
assert "output" in data
|
||||
assert isinstance(data["output"], str)
|
||||
|
||||
@pytest.mark.parametrize("ip,expected_status", [
|
||||
("8.8.8.8", 200),
|
||||
("256.256.256.256", 400), # Invalid IP
|
||||
("", 400), # Missing IP
|
||||
])
|
||||
def test_nslookup_endpoint(client, api_token, ip, expected_status):
|
||||
payload = {"devLastIP": ip} if ip else {}
|
||||
resp = client.post("/nettools/nslookup", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
assert resp.status_code == expected_status
|
||||
data = resp.json
|
||||
|
||||
if expected_status == 200:
|
||||
assert data.get("success") is True
|
||||
assert isinstance(data["output"], list)
|
||||
assert all(isinstance(line, str) for line in data["output"])
|
||||
else:
|
||||
assert data.get("success") is False
|
||||
assert "error" in data
|
||||
|
||||
@pytest.mark.parametrize("ip,mode,expected_status", [
|
||||
("127.0.0.1", "fast", 200),
|
||||
("127.0.0.1", "normal", 200),
|
||||
("127.0.0.1", "detail", 200),
|
||||
("127.0.0.1", "skipdiscovery", 200),
|
||||
("127.0.0.1", "invalidmode", 400),
|
||||
("999.999.999.999", "fast", 400),
|
||||
])
|
||||
def test_nmap_endpoint(client, api_token, ip, mode, expected_status):
|
||||
payload = {"scan": ip, "mode": mode}
|
||||
resp = client.post("/nettools/nmap", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
assert resp.status_code == expected_status
|
||||
data = resp.json
|
||||
|
||||
if expected_status == 200:
|
||||
assert data.get("success") is True
|
||||
assert data.get("mode") == mode
|
||||
assert data.get("ip") == ip
|
||||
assert isinstance(data["output"], list)
|
||||
assert all(isinstance(line, str) for line in data["output"])
|
||||
else:
|
||||
assert data.get("success") is False
|
||||
assert "error" in data
|
||||
|
||||
def test_nslookup_unauthorized(client):
|
||||
# No auth headers
|
||||
resp = client.post("/nettools/nslookup", json={"devLastIP": "8.8.8.8"})
|
||||
assert resp.status_code == 403
|
||||
data = resp.json
|
||||
assert data.get("success") is False
|
||||
assert data.get("error") == "Forbidden"
|
||||
|
||||
def test_nmap_unauthorized(client):
|
||||
# No auth headers
|
||||
resp = client.post("/nettools/nmap", json={"scan": "127.0.0.1", "mode": "fast"})
|
||||
assert resp.status_code == 403
|
||||
data = resp.json
|
||||
assert data.get("success") is False
|
||||
assert data.get("error") == "Forbidden"
|
||||
|
||||
|
||||
def test_internet_info_endpoint(client, api_token):
|
||||
resp = client.get("/nettools/internetinfo", headers=auth_headers(api_token))
|
||||
data = resp.json
|
||||
|
||||
if resp.status_code == 200:
|
||||
assert data.get("success") is True
|
||||
assert isinstance(data.get("output"), str)
|
||||
assert len(data["output"]) > 0 # ensure output is not empty
|
||||
else:
|
||||
# Handle errors, e.g., curl failure
|
||||
assert data.get("success") is False
|
||||
assert "error" in data
|
||||
assert "details" in data
|
||||
259
test/api_endpoints/test_sessions_endpoints.py
Normal file
259
test/api_endpoints/test_sessions_endpoints.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from utils.datetime_utils import timeNowTZ, timeNowDB
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
def test_create_device(client, api_token, test_mac):
|
||||
payload = {
|
||||
"createNew": True,
|
||||
"devType": "Test Device",
|
||||
"devOwner": "Unit Test",
|
||||
"devType": "Router",
|
||||
"devVendor": "TestVendor",
|
||||
}
|
||||
resp = client.post(f"/device/{test_mac}", json=payload, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
# -----------------------------
|
||||
def test_create_session(client, api_token, test_mac):
|
||||
payload = {
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.100",
|
||||
"start_time": timeNowDB(),
|
||||
"event_type_conn": "Connected",
|
||||
"event_type_disc": "Disconnected"
|
||||
}
|
||||
resp = client.post("/sessions/create", json=payload, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
|
||||
# -----------------------------
|
||||
def test_list_sessions(client, api_token, test_mac):
|
||||
# Ensure at least one session exists
|
||||
payload = {
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.100",
|
||||
"start_time": timeNowDB()
|
||||
}
|
||||
client.post("/sessions/create", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
# List sessions for MAC
|
||||
resp = client.get(f"/sessions/list?mac={test_mac}", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
sessions = resp.json.get("sessions")
|
||||
assert any(ses["ses_MAC"] == test_mac for ses in sessions)
|
||||
|
||||
|
||||
def test_device_sessions_by_period(client, api_token, test_mac):
|
||||
# 1. Create a dummy session so we have data
|
||||
payload = {
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.200",
|
||||
"start_time": timeNowDB()
|
||||
}
|
||||
resp_create = client.post("/sessions/create", json=payload, headers=auth_headers(api_token))
|
||||
assert resp_create.status_code == 200
|
||||
assert resp_create.json.get("success") is True
|
||||
|
||||
# 2. Query sessions for the device with a valid period
|
||||
resp = client.get(
|
||||
f"/sessions/{test_mac}?period=7 days",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
data = resp.json
|
||||
assert data.get("success") is True
|
||||
assert "sessions" in data
|
||||
|
||||
sessions = data["sessions"]
|
||||
|
||||
print(sessions)
|
||||
print(test_mac)
|
||||
|
||||
assert isinstance(sessions, list)
|
||||
assert any(s["ses_MAC"] == test_mac for s in sessions)
|
||||
|
||||
|
||||
def test_device_session_events(client, api_token, test_mac):
|
||||
"""
|
||||
Test fetching session/events from the /sessions/session-events endpoint.
|
||||
"""
|
||||
|
||||
# 1. Create a dummy session to ensure we have data
|
||||
payload = {
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.250",
|
||||
"start_time": timeNowDB()
|
||||
}
|
||||
resp_create = client.post(
|
||||
"/sessions/create",
|
||||
json=payload,
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp_create.status_code == 200
|
||||
assert resp_create.json.get("success") is True
|
||||
|
||||
# 2. Fetch session events with default type ('all') and period ('7 days')
|
||||
resp = client.get(
|
||||
f"/sessions/session-events?type=all&period=7 days",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp.status_code == 200
|
||||
|
||||
data = resp.json
|
||||
assert "data" in data # table data key
|
||||
events = data["data"]
|
||||
|
||||
# 3. Validate the response structure
|
||||
assert isinstance(events, list)
|
||||
|
||||
# If there is at least one row, check fields for sessions
|
||||
if events:
|
||||
row = events[0]
|
||||
# Expecting row as list with at least expected columns
|
||||
assert isinstance(row, list)
|
||||
# IP and datetime fields should exist
|
||||
assert row[9] # IP column
|
||||
assert row[3] # Event datetime column
|
||||
|
||||
# 4. Optionally, test filtering by session type
|
||||
resp_sessions = client.get(
|
||||
"/sessions/session-events?type=sessions&period=7 days",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
assert resp_sessions.status_code == 200
|
||||
sessions = resp_sessions.json["data"]
|
||||
assert isinstance(sessions, list)
|
||||
|
||||
# -----------------------------
|
||||
def test_delete_session(client, api_token, test_mac):
|
||||
# First create session
|
||||
payload = {
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.100",
|
||||
"start_time": timeNowDB()
|
||||
}
|
||||
client.post("/sessions/create", json=payload, headers=auth_headers(api_token))
|
||||
|
||||
# Delete session
|
||||
resp = client.delete("/sessions/delete", json={"mac": test_mac}, headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
|
||||
# Confirm deletion
|
||||
resp = client.get(f"/sessions/list?mac={test_mac}", headers=auth_headers(api_token))
|
||||
sessions = resp.json.get("sessions")
|
||||
assert not any(ses["ses_MAC"] == test_mac for ses in sessions)
|
||||
|
||||
|
||||
|
||||
def test_get_sessions_calendar(client, api_token, test_mac):
|
||||
"""
|
||||
Test the /sessions/calendar endpoint.
|
||||
Creates session and ensures the calendar output is correct.
|
||||
Cleans up test sessions after test.
|
||||
"""
|
||||
|
||||
|
||||
# --- Setup: create two sessions for the test MAC ---
|
||||
now = timeNowTZ()
|
||||
start1 = (now - timedelta(days=2)).isoformat(timespec="seconds")
|
||||
end1 = (now - timedelta(days=1, hours=20)).isoformat(timespec="seconds")
|
||||
|
||||
start2 = (now - timedelta(days=1)).isoformat(timespec="seconds")
|
||||
end2 = (now - timedelta(hours=20)).isoformat(timespec="seconds")
|
||||
|
||||
# Create sessions using your endpoint
|
||||
client.post("/sessions/create", json={
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.100",
|
||||
"start_time": start1,
|
||||
"end_time": end1,
|
||||
"event_type_conn": "connect",
|
||||
"event_type_disc": "disconnect"
|
||||
}, headers=auth_headers(api_token))
|
||||
|
||||
client.post("/sessions/create", json={
|
||||
"mac": test_mac,
|
||||
"ip": "192.168.1.100",
|
||||
"start_time": start2,
|
||||
"end_time": end2,
|
||||
"event_type_conn": "connect",
|
||||
"event_type_disc": "disconnect"
|
||||
}, headers=auth_headers(api_token))
|
||||
|
||||
# --- Call the /sessions/calendar API ---
|
||||
start_date = (now - timedelta(days=3)).strftime("%Y-%m-%d")
|
||||
end_date = (now + timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
|
||||
resp = client.get(
|
||||
f"/sessions/calendar?start={start_date}&end={end_date}",
|
||||
headers=auth_headers(api_token)
|
||||
)
|
||||
|
||||
assert resp.status_code == 200
|
||||
data = resp.json
|
||||
assert data.get("success") is True
|
||||
assert "sessions" in data
|
||||
sessions = data["sessions"]
|
||||
|
||||
# --- Verify calendar sessions ---
|
||||
assert len(sessions) >= 2 # at least our two sessions
|
||||
|
||||
# Check expected keys
|
||||
expected_keys = {"resourceId", "title", "start", "end", "color", "tooltip", "className"}
|
||||
for ses in sessions:
|
||||
assert set(ses.keys()) == expected_keys
|
||||
|
||||
# Check that all sessions belong to the test MAC
|
||||
mac_sessions = [ses for ses in sessions if ses["resourceId"] == test_mac]
|
||||
assert len(mac_sessions) == 2 # or exact number if you know it
|
||||
|
||||
# Check ISO date formatting for start/end
|
||||
for ses in mac_sessions:
|
||||
# start must always be present
|
||||
assert ses["start"] is not None, f"Session start is None: {ses}"
|
||||
datetime.fromisoformat(ses["start"])
|
||||
|
||||
# end can be None only if tooltip mentions "<still connected>"
|
||||
if ses["end"] is not None:
|
||||
datetime.fromisoformat(ses["end"])
|
||||
else:
|
||||
assert "<still connected>" in ses["tooltip"], f"End is None but session not marked as still connected: {ses}"
|
||||
|
||||
# --- Cleanup: delete all test sessions for this MAC ---
|
||||
client.delete(f"/sessions/delete?mac={test_mac}", headers=auth_headers(api_token))
|
||||
54
test/api_endpoints/test_settings_endpoints.py
Normal file
54
test/api_endpoints/test_settings_endpoints.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import sys
|
||||
import pathlib
|
||||
import sqlite3
|
||||
import random
|
||||
import string
|
||||
import uuid
|
||||
import os
|
||||
import pytest
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
INSTALL_PATH = os.getenv('NETALERTX_APP', '/app')
|
||||
sys.path.extend([f"{INSTALL_PATH}/front/plugins", f"{INSTALL_PATH}/server"])
|
||||
|
||||
from helper import get_setting_value
|
||||
from api_server.api_server_start import app
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def api_token():
|
||||
return get_setting_value("API_TOKEN")
|
||||
|
||||
@pytest.fixture
|
||||
def client():
|
||||
with app.test_client() as client:
|
||||
yield client
|
||||
|
||||
@pytest.fixture
|
||||
def test_mac():
|
||||
# Generate a unique MAC for each test run
|
||||
return "AA:BB:CC:" + ":".join(f"{random.randint(0,255):02X}" for _ in range(3))
|
||||
|
||||
def auth_headers(token):
|
||||
return {"Authorization": f"Bearer {token}"}
|
||||
|
||||
def test_get_setting_unauthorized(client):
|
||||
resp = client.get("/settings/API_TOKEN") # no auth header
|
||||
assert resp.status_code == 403
|
||||
assert resp.json.get("error") == "Forbidden"
|
||||
|
||||
|
||||
def test_get_setting_valid_key(client, api_token):
|
||||
# We know API_TOKEN exists in settings
|
||||
resp = client.get("/settings/API_TOKEN", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
# The value should equal the token itself
|
||||
assert resp.json.get("value") == api_token
|
||||
|
||||
|
||||
def test_get_setting_invalid_key(client, api_token):
|
||||
resp = client.get("/settings/DOES_NOT_EXIST", headers=auth_headers(api_token))
|
||||
assert resp.status_code == 200
|
||||
assert resp.json.get("success") is True
|
||||
# Depending on implementation, might be None or ""
|
||||
assert resp.json.get("value") in (None, "")
|
||||
Reference in New Issue
Block a user