280 lines
12 KiB
Python
280 lines
12 KiB
Python
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'payload', 'user', 'remote_access', 'pager-webui'))
|
|
import server
|
|
|
|
|
|
def setUpModule():
|
|
__import__('importlib').reload(server)
|
|
|
|
|
|
class PayloadsProxyTest(unittest.TestCase):
|
|
def test_index_proxies(self):
|
|
server.daemon_call = lambda m, p, body=None, token=None, timeout=15: (200, {'payloads': []})
|
|
server.current_token = lambda: 'tok'
|
|
class Ctx:
|
|
args = ()
|
|
status, payload = server.h_payloads_index(Ctx())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload, {'payloads': []})
|
|
|
|
def test_install_uses_key(self):
|
|
calls = []
|
|
def fake(m, p, body=None, token=None, timeout=15):
|
|
calls.append((m, p))
|
|
return 200, {'ok': True}
|
|
server.daemon_call = fake
|
|
server.current_token = lambda: 'tok'
|
|
server.h_payloads_install(type('C', (), {'args': (), 'body': {'key': 'nautilus'}})())
|
|
self.assertTrue(any(m == 'POST' and '/api/payloads/portal/nautilus/install' in p for m, p in calls))
|
|
|
|
def test_remove_uses_key(self):
|
|
calls = []
|
|
def fake(m, p, body=None, token=None, timeout=15):
|
|
calls.append((m, p))
|
|
return 200, {'ok': True}
|
|
server.daemon_call = fake
|
|
server.current_token = lambda: 'tok'
|
|
server.h_payloads_remove(type('C', (), {'args': (), 'body': {'key': 'nautilus'}})())
|
|
self.assertTrue(any(m == 'POST' and '/api/payloads/portal/nautilus/remove' in p for m, p in calls))
|
|
|
|
def test_installed_inventory_flattens_firmware_records(self):
|
|
old = server._payload_daemon
|
|
server._payload_daemon = lambda method, path, body=None: (200, [{
|
|
'installed': {'key': 'user~general~demo', 'title': 'Demo',
|
|
'path': '/root/payloads/user/general/demo',
|
|
'category': 'general', 'launchpoint': 'payload.sh'},
|
|
'missingmanifest': False, 'update': {'version': '2.0'}}])
|
|
try:
|
|
status, payload = server.h_payloads_installed(type('C', (), {'args': ()})())
|
|
finally:
|
|
server._payload_daemon = old
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload['payloads'][0]['title'], 'Demo')
|
|
self.assertEqual(payload['payloads'][0]['update']['version'], '2.0')
|
|
|
|
def test_install_rejects_unsafe_key(self):
|
|
status, payload = server.h_payloads_install(type('C', (), {
|
|
'args': (), 'body': {'key': '../payload'}})())
|
|
self.assertEqual(status, 400)
|
|
self.assertIn('key', payload['error'])
|
|
|
|
def test_run_launches_only_resolved_installed_payload(self):
|
|
old_record = server._payload_record
|
|
old_safe = server._payload_safe_launch
|
|
old_popen = server.subprocess.Popen
|
|
old_run_dir = server.PAYLOAD_RUN_DIR
|
|
|
|
class FakeProcess:
|
|
pid = 4321
|
|
def poll(self):
|
|
return None
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
launch = os.path.join(tmp, 'payload.sh')
|
|
with open(launch, 'w') as handle:
|
|
handle.write('#!/bin/sh\n')
|
|
server._payload_record = lambda key: (200, {
|
|
'key': key, 'title': 'Demo', 'path': tmp,
|
|
'launchpoint': 'payload.sh', 'interpreter': 'sh', 'disabled': False})
|
|
server._payload_safe_launch = lambda item: (tmp, launch)
|
|
calls = []
|
|
server.subprocess.Popen = lambda command, **kwargs: calls.append((command, kwargs)) or FakeProcess()
|
|
server.PAYLOAD_RUN_DIR = os.path.join(tmp, 'runs')
|
|
server._payload_runs.clear()
|
|
try:
|
|
status, payload = server.h_payloads_run(type('C', (), {
|
|
'body': {'key': 'user~general~demo'}})())
|
|
finally:
|
|
server._payload_record = old_record
|
|
server._payload_safe_launch = old_safe
|
|
server.subprocess.Popen = old_popen
|
|
server.PAYLOAD_RUN_DIR = old_run_dir
|
|
server._payload_runs.clear()
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(calls[0][0], ['sh', launch])
|
|
self.assertTrue(calls[0][1]['start_new_session'])
|
|
self.assertTrue(payload['run']['running'])
|
|
|
|
def test_stop_rejects_untracked_process(self):
|
|
server._payload_runs.clear()
|
|
status, payload = server.h_payloads_stop(type('C', (), {
|
|
'body': {'id': 'not-tracked'}})())
|
|
self.assertEqual(status, 404)
|
|
|
|
def test_remove_rejects_active_webui(self):
|
|
status, payload = server.h_payloads_remove(type('C', (), {
|
|
'args': (), 'body': {'key': server.SELF_PAYLOAD_KEY}})())
|
|
self.assertEqual(status, 409)
|
|
self.assertIn('cannot remove itself', payload['error'])
|
|
|
|
|
|
class LoggingTest(unittest.TestCase):
|
|
def test_system_tails(self):
|
|
server.device_run = lambda args, timeout=20: (0, '\n'.join('line%d' % i for i in range(20)), '')
|
|
status, payload = server.h_logging_system(type('C', (), {'args': (), 'query': {'lines': '5'}})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(len(payload['lines']), 5)
|
|
|
|
def test_invalid_line_count_uses_default(self):
|
|
server.device_run = lambda args, timeout=20: (
|
|
0, '\n'.join('line%d' % i for i in range(250)), '')
|
|
status, payload = server.h_logging_system(type('C', (), {
|
|
'args': (), 'query': {'lines': 'invalid'}})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(len(payload['lines']), 200)
|
|
|
|
def test_line_count_is_bounded(self):
|
|
self.assertEqual(server._line_count(
|
|
type('C', (), {'query': {'lines': '999999'}})(), 200), 2000)
|
|
self.assertEqual(server._line_count(
|
|
type('C', (), {'query': {'lines': '-10'}})(), 200), 0)
|
|
|
|
|
|
class SettingsTest(unittest.TestCase):
|
|
def test_hostname_get(self):
|
|
class H:
|
|
command = 'GET'
|
|
server.device_run = lambda args, timeout=20: (0, 'pager\n', '')
|
|
status, payload = server.h_settings_hostname(type('C', (), {'args': (), 'h': H()})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload['hostname'], 'pager')
|
|
|
|
def test_internet_status_uses_bounded_connectivity_check(self):
|
|
calls = []
|
|
old = server.device_run
|
|
server.device_run = lambda args, timeout=20: (
|
|
calls.append((args, timeout)) or (0, '', ''))
|
|
try:
|
|
status, payload = server.h_settings_internet(type('C', (), {})())
|
|
finally:
|
|
server.device_run = old
|
|
self.assertEqual(status, 200)
|
|
self.assertTrue(payload['online'])
|
|
self.assertEqual(calls[0][0][-1], '1.1.1.1')
|
|
self.assertEqual(calls[0][1], 5)
|
|
|
|
def test_reboot_is_scheduled_after_response(self):
|
|
scheduled = []
|
|
class FakeTimer:
|
|
def __init__(self, delay, fn):
|
|
scheduled.append((delay, fn))
|
|
self.daemon = False
|
|
def start(self):
|
|
scheduled.append('started')
|
|
old = server.threading.Timer
|
|
server.threading.Timer = FakeTimer
|
|
try:
|
|
status, payload = server.h_settings_reboot(type('C', (), {})())
|
|
finally:
|
|
server.threading.Timer = old
|
|
self.assertEqual(status, 200)
|
|
self.assertTrue(payload['ok'])
|
|
self.assertEqual(scheduled[0][0], 1.0)
|
|
self.assertEqual(scheduled[1], 'started')
|
|
|
|
def test_password_uses_passwd_stdin_and_invalidates_session(self):
|
|
calls = []
|
|
def fake(args, timeout=20, input_data=None):
|
|
calls.append((args, timeout, input_data))
|
|
return 0, '', ''
|
|
old_run = server.device_run
|
|
old_session = server.SESSION_FILE
|
|
fd, session = tempfile.mkstemp()
|
|
os.close(fd)
|
|
server.device_run = fake
|
|
server.SESSION_FILE = session
|
|
try:
|
|
status, payload = server.h_settings_password(
|
|
type('C', (), {'args': (), 'body': {'password': 'newpw'}})())
|
|
finally:
|
|
server.device_run = old_run
|
|
server.SESSION_FILE = old_session
|
|
self.assertEqual((status, payload), (200, {'ok': True}))
|
|
self.assertEqual(calls, [(['/bin/passwd', 'root'], 15, b'newpw\nnewpw\n')])
|
|
self.assertFalse(os.path.exists(session))
|
|
|
|
def test_password_reports_passwd_failure(self):
|
|
old_run = server.device_run
|
|
server.device_run = lambda args, timeout=20, input_data=None: (1, '', 'bad password')
|
|
try:
|
|
status, payload = server.h_settings_password(
|
|
type('C', (), {'args': (), 'body': {'password': 'newpw'}})())
|
|
finally:
|
|
server.device_run = old_run
|
|
self.assertEqual(status, 500)
|
|
self.assertEqual(payload['error'], 'bad password')
|
|
|
|
def test_ntp_get_parses(self):
|
|
class H:
|
|
command = 'GET'
|
|
server.device_run = lambda args, timeout=20: (0, "system.ntp.enabled='1'\nsystem.ntp.server='0.pool.ntp.org'\n", '')
|
|
status, payload = server.h_settings_ntp(type('C', (), {'args': (), 'h': H()})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload['enabled'], True)
|
|
self.assertIn('0.pool.ntp.org', payload['servers'])
|
|
|
|
def test_ntp_get_parses_multiple_values_on_one_uci_line(self):
|
|
class H:
|
|
command = 'GET'
|
|
server.device_run = lambda args, timeout=20: (
|
|
0,
|
|
"system.ntp.enabled='1'\n"
|
|
"system.ntp.server='0.openwrt.pool.ntp.org' '1.openwrt.pool.ntp.org'\n",
|
|
'')
|
|
status, payload = server.h_settings_ntp(type('C', (), {'args': (), 'h': H()})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload['servers'], [
|
|
'0.openwrt.pool.ntp.org', '1.openwrt.pool.ntp.org'])
|
|
|
|
def test_service_running_detection(self):
|
|
server.device_run = lambda args, timeout=20: (0, '', '')
|
|
status, payload = server.h_settings_service(type('C', (), {'args': ()})())
|
|
self.assertEqual(status, 200)
|
|
self.assertIn('running', payload)
|
|
self.assertIn('background', payload)
|
|
|
|
def test_resources_parses_mark_style_filesystem_rows(self):
|
|
sample = ("Filesystem Type 1024-blocks Used Available Capacity Mounted on\n"
|
|
"/dev/root squashfs 30720 30720 0 100% /rom\n"
|
|
"/dev/mmcblk0p1 ext4 3724208 21876 3492668 1% /mmc\n")
|
|
rows = server._parse_df_rows(sample)
|
|
self.assertEqual(len(rows), 2)
|
|
self.assertEqual(rows[1]['format'], 'ext4')
|
|
self.assertEqual(rows[1]['mount'], '/mmc')
|
|
self.assertEqual(rows[1]['size'], 3724208 * 1024)
|
|
|
|
def test_usb_parser_returns_structured_devices(self):
|
|
server.device_run = lambda args, timeout=20: (
|
|
0, 'Bus 001 Device 004: ID 0e8d:7961 MediaTek Inc. Wireless_Device\n', '')
|
|
status, payload = server.h_settings_usb(type('C', (), {'args': ()})())
|
|
self.assertEqual(status, 200)
|
|
self.assertEqual(payload['devices'][0]['id'], '0e8d:7961')
|
|
self.assertIn('MediaTek', payload['devices'][0]['name'])
|
|
|
|
def test_password_rejects_mismatched_confirmation(self):
|
|
status, payload = server.h_settings_password(type('C', (), {'body': {
|
|
'new_password': 'newpass', 'repeat_password': 'different'}})())
|
|
self.assertEqual(status, 400)
|
|
self.assertIn('match', payload['error'])
|
|
|
|
def test_management_wifi_get_hides_existing_password(self):
|
|
old = server._uci_wifi_iface
|
|
server._uci_wifi_iface = lambda name: {
|
|
'ssid': 'Pager Mgmt', 'key': 'secretpass', 'disabled': '0', 'hidden': '1'}
|
|
try:
|
|
status, payload = server.h_settings_management_wifi(
|
|
type('C', (), {'body': {}, 'args': ()})())
|
|
finally:
|
|
server._uci_wifi_iface = old
|
|
self.assertEqual(status, 200)
|
|
self.assertTrue(payload['has_password'])
|
|
self.assertNotIn('password', payload)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|