From 05b9e0eb180c0cf78f97d6eb4f2d43e14bbaa10d Mon Sep 17 00:00:00 2001 From: Daniel Markstedt Date: Thu, 7 Dec 2023 17:38:24 -0800 Subject: [PATCH] Web UI: Rework the Attach Device section to be universal (#1393) * Correct German translation for Key * Web UI: Rework the Attach Device section to be universal * Web UI: Warn when working dirs are missing * Refactor tests to use global endpoint constants * Add fallback for unknown disk type devices * Rearrange the index page sections * Move Macproxy help text to admins page * Remove image list exception for SCHD * Show Settings button when auth is diabled * Tweak CSS styles for both themes * Move Eject action next to the file name, and improve UI labels --- .../web/src/static/themes/classic/style.css | 9 + python/web/src/static/themes/modern/style.css | 26 +- python/web/src/templates/admin.html | 2 + python/web/src/templates/base.html | 50 +- python/web/src/templates/index.html | 429 ++++++++++-------- .../translations/de/LC_MESSAGES/messages.po | 2 +- python/web/src/web.py | 97 +--- python/web/src/web_utils.py | 15 +- python/web/tests/api/conftest.py | 43 +- python/web/tests/api/test_auth.py | 11 +- python/web/tests/api/test_devices.py | 49 +- python/web/tests/api/test_files.py | 60 ++- python/web/tests/api/test_misc.py | 29 +- python/web/tests/api/test_settings.py | 61 +-- 14 files changed, 455 insertions(+), 428 deletions(-) diff --git a/python/web/src/static/themes/classic/style.css b/python/web/src/static/themes/classic/style.css index d77de45a..de672365 100644 --- a/python/web/src/static/themes/classic/style.css +++ b/python/web/src/static/themes/classic/style.css @@ -58,6 +58,15 @@ div.footer div.theme-change-hint { margin-bottom: 15px; } +div.login-status { + text-align: right; +} + +div.login-status a { + color: white; + text-decoration: underline; +} + div.logged-in { background-color: green; } diff --git a/python/web/src/static/themes/modern/style.css b/python/web/src/static/themes/modern/style.css index ad993af2..4aa05505 100644 --- a/python/web/src/static/themes/modern/style.css +++ b/python/web/src/static/themes/modern/style.css @@ -282,18 +282,6 @@ div.header div.login-form-title { display: none; } -div.header div.authentication-disabled span.separator { - display: none; -} - -div.header div.authentication-disabled span.wiki-help-text { - display: block; -} - -div.header div.authentication-disabled a { - color: #fff; -} - @media (max-width: 900px) { div.header { flex-wrap: wrap; @@ -663,10 +651,11 @@ table#attached-devices td.actions { table#attached-devices td.parameters form { display: flex; + align-items: center; } table#attached-devices td.parameters form label { - display: none; + padding: 0 0.5rem 0 0; } table#attached-devices td.parameters form select { @@ -775,6 +764,11 @@ section#files p { margin-top: 1rem; } +section#files details.subdir { + padding-left: 1rem; + padding-right: 1rem; +} + section#files details.subdir summary.dirname { text-decoration: underline; font-family: monospace; @@ -864,7 +858,7 @@ section#upload a p { /* ------------------------------------------------------------------------------ - Index > Section: Attach peripheral devices + Index > Section: Attach devices ------------------------------------------------------------------------------ */ section#attach-devices table th:last-child, @@ -876,6 +870,10 @@ section#attach-devices form { display: block; } +section#attach-devices table form select.table-dropdown { + width: 16rem; +} + @media (max-width: 900px) { section#attach-devices table tr th:nth-child(2), section#attach-devices table tr td:nth-child(2) { diff --git a/python/web/src/templates/admin.html b/python/web/src/templates/admin.html index 61fd7a63..bb1b6d91 100644 --- a/python/web/src/templates/admin.html +++ b/python/web/src/templates/admin.html @@ -115,6 +115,8 @@ +{% if env["cfg_dir_exists"] %}

@@ -54,6 +55,13 @@

+{% else %} + +
+{{ _("Please create the PiSCSI configuration dir to use configurations:")}} {{ CFG_DIR }} +
+ +{% endif %} @@ -82,13 +90,12 @@ {% endif %} @@ -135,13 +149,6 @@
{{ device.device_name }} - {% if "No Media" in device.status %} + {% if "No Media" in device.status %}
+ - - - +
{% else %} {% if device.params %} @@ -120,7 +127,14 @@ {% endif %} {% endfor %} {% elif device.file %} - {{ device.file }} +
+ + {% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %} + + + + {% endif %} +
{% endif %} {% endif %}
{% if device.id in scsi_ids["occupied_ids"] %} - {% if device.device_type in REMOVABLE_DEVICE_TYPES and "No Media" not in device.status %} -
- - - -
- {% endif %}
@@ -209,12 +216,8 @@ -{% if not files|length: %} -
- {{ _("The images directory is currently empty.") }} -
-{% else %} - +{% if env["image_dir_exists"] %} +{% if files|length %}
{% for subdir, group in formatted_image_files.items() %} @@ -309,7 +312,6 @@ {% else %} - + + + + + + {% for type in device_types.keys() %} + + + + + + {% endfor %} +
{{ _("Device") }}{{ _("Key") }}{{ _("Actions") }}
+ {% if device_types[type]["name"] == type %} + {% if type in REMOVABLE_DEVICE_TYPES %} +
{{ _("Unknown Removable Disk Drive") }}
+ {% elif type in DISK_DEVICE_TYPES %} +
{{ _("Unknown Fixed Disk Drive") }}
+ {% else %} +
{{ _("Unknown Device") }}
+ {% endif %} + {% else %} +
{{ device_types[type]["name"] }}
+ {% endif %} +
+
{{ type }}
+
+ + + {% for key, value in device_types[type]["params"] | dictsort %} + + {% if value.isnumeric() %} + + {% elif key == "interface" %} + + {% else %} + + {% endif %} + {% endfor %} + {% if type in DISK_DEVICE_TYPES %} + + + + + {% endif %} + + + + + + +

@@ -432,6 +596,69 @@
+
+
+ + {{ _("Create Empty Disk Image") }} + +
    +
  • {{ _("Please refer to wiki documentation to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}
  • +
  • {{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}
  • +
+
+ +
+ + + + + + + + + + + +
+
+ +
+

{{ _("Create Disk Image With Properties") }}

+
+ +
+
@@ -507,166 +734,4 @@
-
-
- - {{ _("Create Empty Disk Image") }} - -
    -
  • {{ _("Please refer to wiki documentation to learn more about the supported image file types.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types#image-types") }}
  • -
  • {{ _("It is not recommended to use the Lido hard disk driver with the Macintosh Plus.") }}
  • -
-
- -
- - - - - - - - - - - -
-
- -
-

{{ _("Create Disk Image With Properties") }}

-
- -
- -
-
- - {{ _("Attach Peripheral Device") }} - -
    - - {% if bridge_configured %} -
  • {{ _("The piscsi_bridge network bridge is active and ready to be used by an emulated network adapter!") }}
  • - {% else %} -
  • {{ _("Please configure the piscsi_bridge network bridge before attaching an emulated network adapter!") }}
  • - {% endif %} -
  • {{ _("To browse the modern web, install a vintage web proxy such as Macproxy.", url="https://github.com/PiSCSI/piscsi/wiki/Vintage-Web-Proxy#macproxy") }}
  • - -
  • {{ _("Read more about supported device types on the wiki.", url="https://github.com/PiSCSI/piscsi/wiki/Supported-Device-Types") }} -
  • -
-
- - - - - - - {% for type in REMOVABLE_DEVICE_TYPES + PERIPHERAL_DEVICE_TYPES %} - - - - - - {% endfor %} -
{{ _("Device") }}{{ _("Key") }}{{ _("Parameters and Actions") }}
-
{{ device_types[type]["name"] }}
-
-
{{ type }}
-
-
- - {% for key, value in device_types[type]["params"] | dictsort %} - - {% if value.isnumeric() %} - - {% elif key == "interface" %} - - {% else %} - - {% endif %} - {% endfor %} - {% if type in REMOVABLE_DEVICE_TYPES %} - - - {% endif %} - - - - - -
-
-
- -
{% endblock content %} diff --git a/python/web/src/translations/de/LC_MESSAGES/messages.po b/python/web/src/translations/de/LC_MESSAGES/messages.po index 9c6fabd7..6b2391cf 100644 --- a/python/web/src/translations/de/LC_MESSAGES/messages.po +++ b/python/web/src/translations/de/LC_MESSAGES/messages.po @@ -1309,7 +1309,7 @@ msgstr "" #: src/templates/index.html:594 msgid "Key" -msgstr "Taste" +msgstr "Kürzel" #: src/templates/index.html:595 msgid "Parameters and Actions" diff --git a/python/web/src/web.py b/python/web/src/web.py index fbed803f..10788101 100644 --- a/python/web/src/web.py +++ b/python/web/src/web.py @@ -46,7 +46,6 @@ from return_code_mapper import ReturnCodeMapper from socket_cmds_flask import SocketCmdsFlask from web_utils import ( - working_dirs_exist, sort_and_format_devices, get_valid_scsi_ids, map_device_types_and_names, @@ -125,6 +124,9 @@ def get_env_info(): "image_dir": server_info["image_dir"], "image_root_dir": Path(server_info["image_dir"]).name, "shared_root_dir": Path(FILE_SERVER_DIR).name, + "image_dir_exists": Path(server_info["image_dir"]).exists(), + "cfg_dir_exists": Path(CFG_DIR).exists(), + "hd_suffixes": tuple(server_info["schd"]), "cd_suffixes": tuple(server_info["sccd"]), "rm_suffixes": tuple(server_info["scrm"]), "mo_suffixes": tuple(server_info["scmo"]), @@ -219,7 +221,6 @@ def index(): Sets up data structures for and renders the index page """ server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) devices = piscsi_cmd.list_devices() device_types = map_device_types_and_names(piscsi_cmd.get_device_types()["device_types"]) @@ -304,9 +305,6 @@ def drive_list(): """ Sets up the data structures and kicks off the rendering of the drive list page """ - server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) - return response( template="drives.html", page_title=_("PiSCSI Create Drive"), @@ -342,7 +340,6 @@ def upload_page(): Sets up the data structures and kicks off the rendering of the file uploading page """ server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) return response( template="upload.html", @@ -544,7 +541,6 @@ def show_diskinfo(): if not safe_path["status"]: return response(error=True, message=safe_path["msg"]) server_info = piscsi_cmd.get_server_info() - working_dirs_exist((server_info["image_dir"], CFG_DIR)) returncode, diskinfo = sys_cmd.get_diskinfo(Path(server_info["image_dir"]) / file_name) if returncode == 0: return response( @@ -647,16 +643,17 @@ def log_level(): return response(error=True, message=process["msg"]) -@APP.route("/scsi/attach_device", methods=["POST"]) +@APP.route("/scsi/attach", methods=["POST"]) @login_required def attach_device(): """ - Attaches a peripheral device that doesn't take an image file as argument + Attaches device of any type """ scsi_id = request.form.get("scsi_id") unit = request.form.get("unit") device_type = request.form.get("type") drive_name = request.form.get("drive_name") + file_name = request.form.get("file_name") if not scsi_id: return response(error=True, message=_("No SCSI ID specified")) @@ -690,11 +687,29 @@ def attach_device(): "device_type": device_type, "params": params, } + + if file_name: + kwargs["params"]["file"] = file_name + + # If drive_props is defined use properies from this dict, + # otherwise fall back to the properties file if it exists if drive_props: kwargs["vendor"] = drive_props["vendor"] kwargs["product"] = drive_props["product"] kwargs["revision"] = drive_props["revision"] kwargs["block_size"] = drive_props["block_size"] + else: + drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}" + if drive_properties.is_file(): + process = file_cmd.read_drive_properties(drive_properties) + process = ReturnCodeMapper.add_msg(process) + if not process["status"]: + return response(error=True, message=process["msg"]) + conf = process["conf"] + kwargs["vendor"] = conf["vendor"] + kwargs["product"] = conf["product"] + kwargs["revision"] = conf["revision"] + kwargs["block_size"] = conf["block_size"] process = piscsi_cmd.attach_device(scsi_id, **kwargs) process = ReturnCodeMapper.add_msg(process) @@ -711,70 +726,6 @@ def attach_device(): return response(error=True, message=process["msg"]) -@APP.route("/scsi/attach", methods=["POST"]) -@login_required -def attach_image(): - """ - Attaches a file image as a device - """ - file_name = request.form.get("file_name") - file_size = request.form.get("file_size") - scsi_id = request.form.get("scsi_id") - unit = request.form.get("unit") - device_type = request.form.get("type") - - if not scsi_id: - return response(error=True, message=_("No SCSI ID specified")) - if not file_name: - return response(error=True, message=_("No image file to insert")) - - kwargs = {"unit": int(unit), "params": {"file": file_name}} - - if device_type: - kwargs["device_type"] = device_type - device_types = piscsi_cmd.get_device_types() - expected_block_size = min(device_types["device_types"][device_type]["block_sizes"]) - - # Attempt to load the device properties file: - # same file name with PROPERTIES_SUFFIX appended - drive_properties = Path(CFG_DIR) / f"{file_name}.{PROPERTIES_SUFFIX}" - if drive_properties.is_file(): - process = file_cmd.read_drive_properties(drive_properties) - process = ReturnCodeMapper.add_msg(process) - if not process["status"]: - return response(error=True, message=process["msg"]) - conf = process["conf"] - kwargs["vendor"] = conf["vendor"] - kwargs["product"] = conf["product"] - kwargs["revision"] = conf["revision"] - kwargs["block_size"] = conf["block_size"] - expected_block_size = conf["block_size"] - - process = piscsi_cmd.attach_device(scsi_id, **kwargs) - process = ReturnCodeMapper.add_msg(process) - if process["status"]: - if int(file_size) % int(expected_block_size): - logging.warning( - "The image file size %s bytes is not a multiple of %s. " - "PiSCSI will ignore the trailing data. " - "The image may be corrupted, so proceed with caution.", - file_size, - expected_block_size, - ) - return response( - message=_( - "Attached %(file_name)s as %(device_type)s to " - "SCSI ID %(id_number)s LUN %(unit_number)s", - file_name=file_name, - device_type=get_device_name(device_type), - id_number=scsi_id, - unit_number=unit, - ) - ) - - return response(error=True, message=process["msg"]) - - @APP.route("/scsi/detach_all", methods=["POST"]) @login_required def detach_all_devices(): diff --git a/python/web/src/web_utils.py b/python/web/src/web_utils.py index bc779836..9b22e45b 100644 --- a/python/web/src/web_utils.py +++ b/python/web/src/web_utils.py @@ -8,26 +8,13 @@ from pathlib import Path from ua_parser import user_agent_parser from re import findall -from flask import request, abort +from flask import request from flask_babel import _ from werkzeug.utils import secure_filename from piscsi.sys_cmds import SysCmds -def working_dirs_exist(working_dirs): - """ - Method for validating that working dirs exist. - Takes (tuple) of (str) working_dirs with paths to required dirs. - """ - for dir_path in working_dirs: - if not Path(dir_path).exists(): - abort( - 503, - _(f"Please create directory: {dir_path}"), - ) - - def get_valid_scsi_ids(devices, reserved_ids): """ Takes a list of (dict)s devices, and list of (int)s reserved_ids. diff --git a/python/web/tests/api/conftest.py b/python/web/tests/api/conftest.py index a254b1a8..56116f87 100644 --- a/python/web/tests/api/conftest.py +++ b/python/web/tests/api/conftest.py @@ -8,6 +8,41 @@ FILE_SIZE_1_MIB = 1048576 STATUS_SUCCESS = "success" STATUS_ERROR = "error" +ENV_ENDPOINT = "/env" +HEALTHCHECK_ENDPOINT = "/healthcheck" +PWA_FAVICON_ENDPOINT = "/pwa/favicon.ico" +LOGIN_ENDPOINT = "/login" +LOGOUT_ENDPOINT = "/logout" +ATTACH_ENDPOINT = "/scsi/attach" +DETACH_ENDPOINT = "/scsi/detach" +DETACH_ALL_ENDPOINT = "/scsi/detach_all" +EJECT_ENDPOINT = "/scsi/eject" +RESERVE_ENDPOINT = "/scsi/reserve" +RELEASE_ENDPOINT = "/scsi/release" +INFO_ENDPOINT = "/scsi/info" +CREATE_ENDPOINT = "/files/create" +RENAME_ENDPOINT = "/files/rename" +COPY_ENDPOINT = "/files/copy" +DELETE_ENDPOINT = "/files/delete" +DOWNLOAD_URL_ENDPOINT = "/files/download_url" +DOWNLOAD_IMAGE_ENDPOINT = "/files/download_image" +DOWNLOAD_CONFIG_ENDPOINT = "/files/download_config" +EXTRACT_IMAGE_ENDPOINT = "/files/extract_image" +UPLOAD_ENDPOINT = "/files/upload" +CREATE_ISO_ENDPOINT = "/files/create_iso" +DISKINFO_ENDPOINT = "/files/diskinfo" +DRIVE_LIST_ENDPOINT = "/drive/list" +DRIVE_CREATE_ENDPOINT = "/drive/create" +DRIVE_CDROM_ENDPOINT = "/drive/cdrom" +MANPAGE_ENDPOINT = "/sys/manpage?app=piscsi" +LANGUAGE_ENDPOINT = "/language" +LOG_LEVEL_ENDPOINT = "/logs/level" +LOG_SHOW_ENDPOINT = "/logs/show" +CONFIG_SAVE_ENDPOINT = "/config/save" +CONFIG_ACTION_ENDPOINT = "/config/action" +THEME_ENDPOINT = "/theme" +SYS_RENAME_ENDPOINT = "/sys/rename" + @pytest.fixture(scope="function") def create_test_image(request, http_client): @@ -18,7 +53,7 @@ def create_test_image(request, http_client): file_name = f"{file_prefix}.{image_type}" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": image_type, @@ -42,7 +77,7 @@ def create_test_image(request, http_client): def delete(): for image in images: - response = http_client.post("/files/delete", data={"file_name": image["file_name"]}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": image["file_name"]}) if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS: warnings.warn( f"Failed to auto-delete file created with create_test_image fixture: {image}" @@ -71,7 +106,7 @@ def list_attached_images(http_client): @pytest.fixture(scope="function") def delete_file(http_client): def delete(file_name): - response = http_client.post("/files/delete", data={"file_name": file_name}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name}) if response.status_code != 200 or response.json()["status"] != STATUS_SUCCESS: warnings.warn(f"Failed to delete file via delete_file fixture: {file_name}") @@ -81,7 +116,7 @@ def delete_file(http_client): @pytest.fixture(scope="function") def detach_devices(http_client): def detach(): - response = http_client.post("/scsi/detach_all") + response = http_client.post(DETACH_ALL_ENDPOINT) if response.json()["status"] == STATUS_SUCCESS: return True raise Exception("Failed to detach SCSI devices") diff --git a/python/web/tests/api/test_auth.py b/python/web/tests/api/test_auth.py index 1261aa58..404df4b6 100644 --- a/python/web/tests/api/test_auth.py +++ b/python/web/tests/api/test_auth.py @@ -1,11 +1,10 @@ -from conftest import STATUS_SUCCESS, STATUS_ERROR +from conftest import STATUS_SUCCESS, STATUS_ERROR, LOGIN_ENDPOINT, LOGOUT_ENDPOINT -# route("/login", methods=["POST"]) def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated): # Note: This test depends on the piscsi group existing and 'username' a member the group response = http_client_unauthenticated.post( - "/login", + LOGIN_ENDPOINT, data={ "username": pytestconfig.getoption("piscsi_username"), "password": pytestconfig.getoption("piscsi_password"), @@ -19,10 +18,9 @@ def test_login_with_valid_credentials(pytestconfig, http_client_unauthenticated) assert "env" in response_data["data"] -# route("/login", methods=["POST"]) def test_login_with_invalid_credentials(http_client_unauthenticated): response = http_client_unauthenticated.post( - "/login", + LOGIN_ENDPOINT, data={ "username": "__INVALID_USER__", "password": "__INVALID_PASS__", @@ -38,7 +36,6 @@ def test_login_with_invalid_credentials(http_client_unauthenticated): ) -# route("/logout") def test_logout(http_client): - response = http_client.get("/logout") + response = http_client.get(LOGOUT_ENDPOINT) assert response.status_code == 200 diff --git a/python/web/tests/api/test_devices.py b/python/web/tests/api/test_devices.py index f13c6887..1b39686a 100644 --- a/python/web/tests/api/test_devices.py +++ b/python/web/tests/api/test_devices.py @@ -2,20 +2,24 @@ import pytest from conftest import ( SCSI_ID, - FILE_SIZE_1_MIB, STATUS_SUCCESS, + ATTACH_ENDPOINT, + DETACH_ENDPOINT, + DETACH_ALL_ENDPOINT, + EJECT_ENDPOINT, + RESERVE_ENDPOINT, + RELEASE_ENDPOINT, + INFO_ENDPOINT, ) -# route("/scsi/attach", methods=["POST"]) -def test_attach_image(http_client, create_test_image, detach_devices): +def test_attach_device_with_image(http_client, create_test_image, detach_devices): test_image = create_test_image() response = http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -26,14 +30,13 @@ def test_attach_image(http_client, create_test_image, detach_devices): assert response.status_code == 200 assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == ( - f"Attached {test_image} as Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0" + f"Attached Hard Disk Drive to SCSI ID {SCSI_ID} LUN 0" ) # Cleanup detach_devices() -# route("/scsi/attach_device", methods=["POST"]) @pytest.mark.parametrize( "device_name,device_config", [ @@ -89,7 +92,7 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con device_config["unit"] = 0 response = http_client.post( - "/scsi/attach_device", + ATTACH_ENDPOINT, data=device_config, ) @@ -105,15 +108,13 @@ def test_attach_device(env, http_client, detach_devices, device_name, device_con detach_devices() -# route("/scsi/detach", methods=["POST"]) def test_detach_device(http_client, create_test_image): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -121,7 +122,7 @@ def test_detach_device(http_client, create_test_image): ) response = http_client.post( - "/scsi/detach", + DETACH_ENDPOINT, data={ "scsi_id": SCSI_ID, "unit": 0, @@ -135,7 +136,6 @@ def test_detach_device(http_client, create_test_image): assert response_data["messages"][0]["message"] == f"Detached SCSI ID {SCSI_ID} LUN 0" -# route("/scsi/detach_all", methods=["POST"]) def test_detach_all_devices(http_client, create_test_image, list_attached_images): test_images = [] scsi_ids = [4, 5, 6] @@ -145,10 +145,9 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images test_images.append(test_image) http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": scsi_id, "unit": 0, "type": "SCHD", @@ -157,7 +156,7 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images assert list_attached_images() == test_images - response = http_client.post("/scsi/detach_all") + response = http_client.post(DETACH_ALL_ENDPOINT) response_data = response.json() assert response.status_code == 200 @@ -165,15 +164,13 @@ def test_detach_all_devices(http_client, create_test_image, list_attached_images assert list_attached_images() == [] -# route("/scsi/eject", methods=["POST"]) def test_eject_device(http_client, create_test_image, detach_devices): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCCD", # CD-ROM @@ -181,7 +178,7 @@ def test_eject_device(http_client, create_test_image, detach_devices): ) response = http_client.post( - "/scsi/eject", + EJECT_ENDPOINT, data={ "scsi_id": SCSI_ID, "unit": 0, @@ -198,15 +195,13 @@ def test_eject_device(http_client, create_test_image, detach_devices): detach_devices() -# route("/scsi/info", methods=["POST"]) def test_show_device_info(http_client, create_test_image, detach_devices): test_image = create_test_image() http_client.post( - "/scsi/attach", + ATTACH_ENDPOINT, data={ "file_name": test_image, - "file_size": FILE_SIZE_1_MIB, "scsi_id": SCSI_ID, "unit": 0, "type": "SCHD", @@ -214,7 +209,7 @@ def test_show_device_info(http_client, create_test_image, detach_devices): ) response = http_client.post( - "/scsi/info", + INFO_ENDPOINT, ) response_data = response.json() @@ -228,13 +223,11 @@ def test_show_device_info(http_client, create_test_image, detach_devices): detach_devices() -# route("/scsi/reserve", methods=["POST"]) -# route("/scsi/release", methods=["POST"]) def test_reserve_and_release_device(http_client): scsi_id = 0 response = http_client.post( - "/scsi/reserve", + RESERVE_ENDPOINT, data={ "scsi_id": scsi_id, "memo": "TEST", @@ -248,7 +241,7 @@ def test_reserve_and_release_device(http_client): assert response_data["messages"][0]["message"] == f"Reserved SCSI ID {scsi_id}" response = http_client.post( - "/scsi/release", + RELEASE_ENDPOINT, data={ "scsi_id": scsi_id, }, diff --git a/python/web/tests/api/test_files.py b/python/web/tests/api/test_files.py index 984cc4ec..a1838c65 100644 --- a/python/web/tests/api/test_files.py +++ b/python/web/tests/api/test_files.py @@ -5,16 +5,26 @@ import os from conftest import ( FILE_SIZE_1_MIB, STATUS_SUCCESS, + CREATE_ENDPOINT, + RENAME_ENDPOINT, + COPY_ENDPOINT, + DELETE_ENDPOINT, + DOWNLOAD_URL_ENDPOINT, + DOWNLOAD_IMAGE_ENDPOINT, + DOWNLOAD_CONFIG_ENDPOINT, + EXTRACT_IMAGE_ENDPOINT, + UPLOAD_ENDPOINT, + CREATE_ISO_ENDPOINT, + DISKINFO_ENDPOINT, ) -# route("/files/create", methods=["POST"]) def test_create_file(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -34,13 +44,12 @@ def test_create_file(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_with_properties(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -64,13 +73,12 @@ def test_create_file_with_properties(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_and_format_hfs(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hda" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hda", @@ -91,7 +99,6 @@ def test_create_file_and_format_hfs(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/create", methods=["POST"]) def test_create_file_and_format_fat(env, http_client, list_files, delete_file): if env["is_docker"]: pytest.skip("Test not supported in Docker environment.") @@ -99,7 +106,7 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file): file_name = f"{file_prefix}.hdr" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hdr", @@ -120,13 +127,12 @@ def test_create_file_and_format_fat(env, http_client, list_files, delete_file): delete_file(file_name) -# route("/files/rename", methods=["POST"]) def test_rename_file(http_client, create_test_image, list_files, delete_file): original_file = create_test_image(auto_delete=False) renamed_file = f"{uuid.uuid4()}.rename" response = http_client.post( - "/files/rename", + RENAME_ENDPOINT, data={"file_name": original_file, "new_file_name": renamed_file}, ) @@ -141,13 +147,12 @@ def test_rename_file(http_client, create_test_image, list_files, delete_file): delete_file(renamed_file) -# route("/files/copy", methods=["POST"]) def test_copy_file(http_client, create_test_image, list_files, delete_file): original_file = create_test_image() copy_file = f"{uuid.uuid4()}.copy" response = http_client.post( - "/files/copy", + COPY_ENDPOINT, data={ "file_name": original_file, "copy_file_name": copy_file, @@ -167,11 +172,10 @@ def test_copy_file(http_client, create_test_image, list_files, delete_file): delete_file(copy_file) -# route("/files/delete", methods=["POST"]) def test_delete_file(http_client, create_test_image, list_files): file_name = create_test_image(auto_delete=False) - response = http_client.post("/files/delete", data={"file_name": file_name}) + response = http_client.post(DELETE_ENDPOINT, data={"file_name": file_name}) response_data = response.json() @@ -181,7 +185,6 @@ def test_delete_file(http_client, create_test_image, list_files): assert file_name not in list_files() -# route("/files/extract_image", methods=["POST"]) @pytest.mark.parametrize( "archive_file_name,image_file_name", [ @@ -205,7 +208,7 @@ def test_extract_file( ) http_client.post( - "/files/download_url", + DOWNLOAD_URL_ENDPOINT, data={ "destination": "disk_images", "images_subdir": "", @@ -214,7 +217,7 @@ def test_extract_file( ) response = http_client.post( - "/files/extract_image", + EXTRACT_IMAGE_ENDPOINT, data={ "archive_file": archive_file_name, "archive_members": image_file_name, @@ -233,7 +236,6 @@ def test_extract_file( delete_file(image_file_name) -# route("/files/upload", methods=["POST"]) def test_upload_file(http_client, delete_file): file_name = f"{uuid.uuid4()}.test" @@ -267,7 +269,7 @@ def test_upload_file(http_client, delete_file): file_data = {"file": (file_name, file.read(chunk_size))} response = http_client.post( - "/files/upload", + UPLOAD_ENDPOINT, data=form_data, files=file_data, ) @@ -283,11 +285,10 @@ def test_upload_file(http_client, delete_file): delete_file(file_name) -# route("/files/download_image", methods=["POST"]) def test_download_image(http_client, create_test_image): file_name = create_test_image() - response = http_client.post("/files/download_image", data={"file": file_name}) + response = http_client.post(DOWNLOAD_IMAGE_ENDPOINT, data={"file": file_name}) assert response.status_code == 200 assert response.headers["content-type"] == "application/octet-stream" @@ -295,13 +296,12 @@ def test_download_image(http_client, create_test_image): assert response.headers["content-length"] == str(FILE_SIZE_1_MIB) -# route("/files/download_config", methods=["POST"]) def test_download_properties(http_client, list_files, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/files/create", + CREATE_ENDPOINT, data={ "file_name": file_prefix, "type": "hds", @@ -321,7 +321,7 @@ def test_download_properties(http_client, list_files, delete_file): ) assert file_name in list_files() - response = http_client.post("/files/download_config", data={"file": f"{file_name}.properties"}) + response = http_client.post(DOWNLOAD_CONFIG_ENDPOINT, data={"file": f"{file_name}.properties"}) assert response.status_code == 200 assert response.headers["content-type"] == "application/octet-stream" @@ -331,7 +331,6 @@ def test_download_properties(http_client, list_files, delete_file): delete_file(file_name) -# route("/files/download_url", methods=["POST"]) def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_file): file_name = str(uuid.uuid4()) http_path = f"/images/{file_name}" @@ -347,7 +346,7 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi ) response = http_client.post( - "/files/download_url", + DOWNLOAD_URL_ENDPOINT, data={ "destination": "disk_images", "images_subdir": subdir, @@ -369,7 +368,6 @@ def test_download_url_to_dir(env, httpserver, http_client, list_files, delete_fi delete_file(file_name) -# route("/files/create_iso", methods=["POST"]) def test_create_iso_from_url( httpserver, http_client, @@ -392,7 +390,7 @@ def test_create_iso_from_url( ) response = http_client.post( - "/files/create_iso", + CREATE_ISO_ENDPOINT, data={ "type": ISO_TYPE, "url": url, @@ -414,7 +412,6 @@ def test_create_iso_from_url( delete_file(iso_file_name) -# route("/files/create_iso", methods=["POST"]) def test_create_iso_from_local_file( http_client, create_test_image, @@ -427,7 +424,7 @@ def test_create_iso_from_local_file( ISO_TYPE = "HFS" response = http_client.post( - "/files/create_iso", + CREATE_ISO_ENDPOINT, data={ "type": ISO_TYPE, "file": test_file_name, @@ -449,12 +446,11 @@ def test_create_iso_from_local_file( delete_file(iso_file_name) -# route("/files/diskinfo", methods=["POST"]) def test_show_diskinfo(http_client, create_test_image): test_image = create_test_image() response = http_client.post( - "/files/diskinfo", + DISKINFO_ENDPOINT, data={ "file_name": test_image, }, diff --git a/python/web/tests/api/test_misc.py b/python/web/tests/api/test_misc.py index 727ffa0e..5d0ef0e7 100644 --- a/python/web/tests/api/test_misc.py +++ b/python/web/tests/api/test_misc.py @@ -3,10 +3,16 @@ import uuid from conftest import ( FILE_SIZE_1_MIB, STATUS_SUCCESS, + ENV_ENDPOINT, + PWA_FAVICON_ENDPOINT, + HEALTHCHECK_ENDPOINT, + DRIVE_LIST_ENDPOINT, + DRIVE_CREATE_ENDPOINT, + DRIVE_CDROM_ENDPOINT, + MANPAGE_ENDPOINT, ) -# route("/") def test_index(http_client): response = http_client.get("/") response_data = response.json() @@ -16,9 +22,8 @@ def test_index(http_client): assert "devices" in response_data["data"] -# route("/env") def test_get_env_info(http_client): - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response.status_code == 200 @@ -26,17 +31,15 @@ def test_get_env_info(http_client): assert "running_env" in response_data["data"] -# route("/pwa/") def test_pwa_route(http_client): - response = http_client.get("/pwa/favicon.ico") + response = http_client.get(PWA_FAVICON_ENDPOINT) assert response.status_code == 200 assert response.headers["content-disposition"] == "inline; filename=favicon.ico" -# route("/drive/list", methods=["GET"]) def test_show_named_drive_presets(http_client): - response = http_client.get("/drive/list") + response = http_client.get(DRIVE_LIST_ENDPOINT) response_data = response.json() prev_drive = {"name": ""} @@ -57,12 +60,11 @@ def test_show_named_drive_presets(http_client): assert "files" in response_data["data"] -# route("/drive/cdrom", methods=["POST"]) def test_create_cdrom_properties_file(env, http_client): file_name = f"{uuid.uuid4()}.iso" response = http_client.post( - "/drive/cdrom", + DRIVE_CDROM_ENDPOINT, data={ "drive_name": "Sony CDU-8012", "file_name": file_name, @@ -78,13 +80,12 @@ def test_create_cdrom_properties_file(env, http_client): ) -# route("/drive/create", methods=["POST"]) def test_create_image_with_properties_file(http_client, delete_file): file_prefix = str(uuid.uuid4()) file_name = f"{file_prefix}.hds" response = http_client.post( - "/drive/create", + DRIVE_CREATE_ENDPOINT, data={ "drive_name": "Miniscribe M8425", "size": FILE_SIZE_1_MIB, @@ -105,16 +106,14 @@ def test_create_image_with_properties_file(http_client, delete_file): delete_file(file_name) -# route("/sys/manpage", methods=["POST"]) def test_show_manpage(http_client): - response = http_client.get("/sys/manpage?app=piscsi") + response = http_client.get(MANPAGE_ENDPOINT) response_data = response.json() assert response.status_code == 200 assert "piscsi" in response_data["data"]["manpage"] -# route("/healthcheck", methods=["GET"]) def test_healthcheck(http_client): - response = http_client.get("/healthcheck") + response = http_client.get(HEALTHCHECK_ENDPOINT) assert response.status_code == 200 diff --git a/python/web/tests/api/test_settings.py b/python/web/tests/api/test_settings.py index 3875fd9e..94da4a51 100644 --- a/python/web/tests/api/test_settings.py +++ b/python/web/tests/api/test_settings.py @@ -1,10 +1,20 @@ import pytest import uuid -from conftest import STATUS_SUCCESS +from conftest import ( + STATUS_SUCCESS, + ENV_ENDPOINT, + LANGUAGE_ENDPOINT, + LOG_LEVEL_ENDPOINT, + LOG_SHOW_ENDPOINT, + CONFIG_SAVE_ENDPOINT, + CONFIG_ACTION_ENDPOINT, + THEME_ENDPOINT, + SYS_RENAME_ENDPOINT, + RESERVE_ENDPOINT, +) -# route("/language", methods=["POST"]) @pytest.mark.parametrize( "locale,confirm_message", [ @@ -18,7 +28,7 @@ from conftest import STATUS_SUCCESS ) def test_set_language(http_client, locale, confirm_message): response = http_client.post( - "/language", + LANGUAGE_ENDPOINT, data={ "locale": locale, }, @@ -31,11 +41,10 @@ def test_set_language(http_client, locale, confirm_message): assert response_data["messages"][0]["message"] == confirm_message -# route("/logs/level", methods=["POST"]) @pytest.mark.parametrize("level", ["trace", "debug", "info", "warn", "err", "off"]) def test_set_log_level(http_client, level): response = http_client.post( - "/logs/level", + LOG_LEVEL_ENDPOINT, data={ "level": level, }, @@ -49,17 +58,16 @@ def test_set_log_level(http_client, level): # Cleanup http_client.post( - "/logs/level", + LOG_LEVEL_ENDPOINT, data={ "level": "debug", }, ) -# route("/logs/show", methods=["POST"]) def test_show_logs(http_client): response = http_client.post( - "/logs/show", + LOG_SHOW_ENDPOINT, data={ "lines": 100, "scope": "piscsi", @@ -73,8 +81,6 @@ def test_show_logs(http_client): assert response_data["data"]["scope"] == "piscsi" -# route("/config/save", methods=["POST"]) -# route("/config/action", methods=["POST"]) def test_save_load_and_delete_configs(env, http_client): config_name = str(uuid.uuid4()) config_json_file = f"{config_name}.json" @@ -86,7 +92,7 @@ def test_save_load_and_delete_configs(env, http_client): # Save the initial state to a config response = http_client.post( - "/config/save", + CONFIG_SAVE_ENDPOINT, data={ "name": config_name, }, @@ -104,7 +110,7 @@ def test_save_load_and_delete_configs(env, http_client): # Modify the state http_client.post( - "/scsi/reserve", + RESERVE_ENDPOINT, data={ "scsi_id": reserved_scsi_id, "memo": reservation_memo, @@ -115,7 +121,7 @@ def test_save_load_and_delete_configs(env, http_client): # Load the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "load": True, @@ -135,7 +141,7 @@ def test_save_load_and_delete_configs(env, http_client): # Delete the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "delete": True, @@ -153,15 +159,13 @@ def test_save_load_and_delete_configs(env, http_client): assert config_json_file not in http_client.get("/").json()["data"]["config_files"] -# route("/config/save", methods=["POST"]) -# route("/config/action", methods=["POST"]) -def test_download_configs(env, http_client, delete_file): +def test_download_configs(env, http_client): config_name = str(uuid.uuid4()) config_json_file = f"{config_name}.json" # Save the initial state to a config response = http_client.post( - "/config/save", + CONFIG_SAVE_ENDPOINT, data={ "name": config_name, }, @@ -172,7 +176,7 @@ def test_download_configs(env, http_client, delete_file): # Download the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "send": True, @@ -185,7 +189,7 @@ def test_download_configs(env, http_client, delete_file): # Delete the saved config response = http_client.post( - "/config/action", + CONFIG_ACTION_ENDPOINT, data={ "name": config_json_file, "delete": True, @@ -196,7 +200,6 @@ def test_download_configs(env, http_client, delete_file): assert config_json_file not in http_client.get("/").json()["data"]["config_files"] -# route("/theme", methods=["POST"]) @pytest.mark.parametrize( "theme", [ @@ -206,7 +209,7 @@ def test_download_configs(env, http_client, delete_file): ) def test_set_theme(http_client, theme): response = http_client.post( - "/theme", + THEME_ENDPOINT, data={ "name": theme, }, @@ -219,7 +222,6 @@ def test_set_theme(http_client, theme): assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." -# route("/theme", methods=["GET"]) @pytest.mark.parametrize( "theme", [ @@ -229,7 +231,7 @@ def test_set_theme(http_client, theme): ) def test_set_theme_via_query_string(http_client, theme): response = http_client.get( - "/theme", + THEME_ENDPOINT, params={ "name": theme, }, @@ -242,17 +244,16 @@ def test_set_theme_via_query_string(http_client, theme): assert response_data["messages"][0]["message"] == f"Theme changed to '{theme}'." -# route("/sys/rename", methods=["POST"]) def test_rename_system(env, http_client): new_name = "SYSTEM NAME TEST" - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() old_name = response_data["data"]["system_name"] response = http_client.post( - "/sys/rename", + SYS_RENAME_ENDPOINT, data={ "system_name": new_name, }, @@ -264,13 +265,13 @@ def test_rename_system(env, http_client): assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == f"System name changed to '{new_name}'." - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response_data["data"]["system_name"] == new_name response = http_client.post( - "/sys/rename", + SYS_RENAME_ENDPOINT, data={ "system_name": old_name, }, @@ -282,7 +283,7 @@ def test_rename_system(env, http_client): assert response_data["status"] == STATUS_SUCCESS assert response_data["messages"][0]["message"] == f"System name changed to '{old_name}'." - response = http_client.get("/env") + response = http_client.get(ENV_ENDPOINT) response_data = response.json() assert response_data["data"]["system_name"] == old_name