firmware-base/scripts/test_api.py

294 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Test script for the ESP32 Modbus REST API
This script tests all endpoints of the REST API and reports the results.
"""
import requests
import json
import argparse
import time
import sys
from colorama import init, Fore, Style
# Initialize colorama for colored terminal output
init()
class ApiTester:
def __init__(self, base_url):
self.base_url = base_url
self.api_url = f"{base_url}/api/v1"
self.success_count = 0
self.fail_count = 0
def print_success(self, message):
print(f"{Fore.GREEN}✓ SUCCESS: {message}{Style.RESET_ALL}")
self.success_count += 1
def print_fail(self, message, error=None):
print(f"{Fore.RED}✗ FAIL: {message}{Style.RESET_ALL}")
if error:
print(f" {Fore.YELLOW}Error: {error}{Style.RESET_ALL}")
self.fail_count += 1
def print_info(self, message):
print(f"{Fore.BLUE} INFO: {message}{Style.RESET_ALL}")
def print_response(self, response):
try:
formatted_json = json.dumps(response.json(), indent=2)
print(f"{Fore.CYAN}Response: {formatted_json}{Style.RESET_ALL}")
except:
print(f"{Fore.CYAN}Response: {response.text}{Style.RESET_ALL}")
def run_tests(self):
"""Run all API tests"""
self.print_info(f"Testing API at {self.api_url}")
print("=" * 80)
# Test system info endpoint
self.test_system_info()
print("-" * 80)
# Test coil endpoints
self.test_coils_list()
print("-" * 80)
self.test_coil_get(30) # Test relay coil 0
print("-" * 80)
self.test_coil_toggle(30) # Toggle relay coil 0
print("-" * 80)
# Test register endpoints
self.test_registers_list()
print("-" * 80)
self.test_register_get(20) # Test battle counter register
print("-" * 80)
self.test_register_update(20, 42) # Update battle counter register
print("-" * 80)
# Test relay test endpoint
self.test_relay_test()
print("=" * 80)
# Print summary
print(f"\nTest Summary: {self.success_count} passed, {self.fail_count} failed")
return self.fail_count == 0
def test_system_info(self):
"""Test the system info endpoint"""
self.print_info("Testing GET /system/info")
try:
response = requests.get(f"{self.api_url}/system/info", timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'version' in data and 'board' in data and 'uptime' in data:
self.print_success("System info endpoint returned valid data")
else:
self.print_fail("System info endpoint returned incomplete data")
else:
self.print_fail(f"System info endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail("Failed to connect to system info endpoint", str(e))
def test_coils_list(self):
"""Test the coils list endpoint"""
self.print_info("Testing GET /coils")
try:
response = requests.get(f"{self.api_url}/coils?start=0&count=10", timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'coils' in data and isinstance(data['coils'], list):
self.print_success("Coils endpoint returned valid data")
else:
self.print_fail("Coils endpoint returned invalid data")
else:
self.print_fail(f"Coils endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail("Failed to connect to coils endpoint", str(e))
def test_coil_get(self, address):
"""Test getting a specific coil"""
self.print_info(f"Testing GET /coils?address={address}")
try:
response = requests.get(f"{self.api_url}/coils", params={"address": address}, timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'address' in data and 'value' in data:
self.print_success(f"Coil {address} endpoint returned valid data")
else:
self.print_fail(f"Coil {address} endpoint returned incomplete data")
else:
self.print_fail(f"Coil {address} endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail(f"Failed to connect to coil {address} endpoint", str(e))
def test_coil_toggle(self, address):
"""Test toggling a coil"""
self.print_info(f"Testing POST /coils/{address}")
# First, get the current value
try:
get_response = requests.get(f"{self.api_url}/coils", params={"address": address}, timeout=5)
if get_response.status_code == 200:
current_value = get_response.json().get('value', False)
new_value = not current_value
# Now toggle the value
try:
self.print_info(f"Setting coil {address} to {new_value}")
post_response = requests.post(
f"{self.api_url}/coils/{address}",
json={"value": new_value},
timeout=5
)
if post_response.status_code == 200:
data = post_response.json()
self.print_response(post_response)
if ('success' in data and data['success'] and
'address' in data and data['address'] == address and
'value' in data and data['value'] == new_value):
self.print_success(f"Successfully toggled coil {address}")
# Toggle back to original state to be nice
time.sleep(1)
requests.post(
f"{self.api_url}/coils/{address}",
json={"value": current_value},
timeout=5
)
self.print_info(f"Reset coil {address} to original state")
else:
self.print_fail(f"Failed to toggle coil {address}")
else:
self.print_fail(f"Coil toggle endpoint returned status code {post_response.status_code}")
except Exception as e:
self.print_fail(f"Failed to toggle coil {address}", str(e))
else:
self.print_fail(f"Failed to get current coil state: {get_response.status_code}")
except Exception as e:
self.print_fail(f"Failed to get current coil state", str(e))
def test_registers_list(self):
"""Test the registers list endpoint"""
self.print_info("Testing GET /registers")
try:
response = requests.get(f"{self.api_url}/registers?start=0&count=10", timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'registers' in data and isinstance(data['registers'], list):
self.print_success("Registers endpoint returned valid data")
else:
self.print_fail("Registers endpoint returned invalid data")
else:
self.print_fail(f"Registers endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail("Failed to connect to registers endpoint", str(e))
def test_register_get(self, address):
"""Test getting a specific register"""
self.print_info(f"Testing GET /registers?address={address}")
try:
response = requests.get(f"{self.api_url}/registers", params={"address": address}, timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'address' in data and 'value' in data:
self.print_success(f"Register {address} endpoint returned valid data")
else:
self.print_fail(f"Register {address} endpoint returned incomplete data")
else:
self.print_fail(f"Register {address} endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail(f"Failed to connect to register {address} endpoint", str(e))
def test_register_update(self, address, new_value):
"""Test updating a register"""
self.print_info(f"Testing POST /registers/{address}")
# First, get the current value
try:
get_response = requests.get(f"{self.api_url}/registers", params={"address": address}, timeout=5)
if get_response.status_code == 200:
current_value = get_response.json().get('value', 0)
# Now update the value
try:
self.print_info(f"Setting register {address} to {new_value}")
post_response = requests.post(
f"{self.api_url}/registers/{address}",
params={"value": new_value},
timeout=5
)
if post_response.status_code == 200:
data = post_response.json()
self.print_response(post_response)
if ('success' in data and data['success'] and
'address' in data and data['address'] == address and
'value' in data and data['value'] == new_value):
self.print_success(f"Successfully updated register {address}")
# Restore original value to be nice
time.sleep(1)
requests.post(
f"{self.api_url}/registers/{address}",
params={"value": current_value},
timeout=5
)
self.print_info(f"Reset register {address} to original value")
else:
self.print_fail(f"Failed to update register {address}")
else:
self.print_fail(f"Register update endpoint returned status code {post_response.status_code}")
except Exception as e:
self.print_fail(f"Failed to update register {address}", str(e))
else:
self.print_fail(f"Failed to get current register value: {get_response.status_code}")
except Exception as e:
self.print_fail(f"Failed to get current register value", str(e))
def test_relay_test(self):
"""Test the relay test endpoint"""
self.print_info("Testing POST /relay/test")
try:
response = requests.post(f"{self.api_url}/relay/test", timeout=5)
if response.status_code == 200:
data = response.json()
self.print_response(response)
if 'success' in data and 'message' in data:
self.print_success("Relay test endpoint returned valid data")
else:
self.print_fail("Relay test endpoint returned incomplete data")
else:
self.print_fail(f"Relay test endpoint returned status code {response.status_code}")
except Exception as e:
self.print_fail("Failed to connect to relay test endpoint", str(e))
def main():
parser = argparse.ArgumentParser(description='Test the ESP32 Modbus REST API')
parser.add_argument('--host', type=str, default='modbus-esp32.local',
help='Hostname or IP address of the ESP32 device (default: modbus-esp32.local)')
parser.add_argument('--port', type=int, default=80,
help='Port number (default: 80)')
parser.add_argument('--protocol', type=str, default='http',
choices=['http', 'https'],
help='Protocol to use (default: http)')
args = parser.parse_args()
base_url = f"{args.protocol}://{args.host}"
if args.port != 80:
base_url += f":{args.port}"
tester = ApiTester(base_url)
success = tester.run_tests()
# Return non-zero exit code if any tests failed
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()