def test_http_server_plan_history(re_manager, fastapi_server):  # noqa F811
    # Select very short plan
    plan = {
        "item": {
            "name": "count",
            "args": [["det1", "det2"]],
            "item_type": "plan"
        }
    }
    request_to_json("post", "/queue/item/add", json=plan)
    request_to_json("post", "/queue/item/add", json=plan)
    request_to_json("post", "/queue/item/add", json=plan)

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    request_to_json("post", "/queue/start")
    ttime.sleep(5)

    resp1 = request_to_json("get", "/history/get")
    assert len(resp1["items"]) == 3
    assert resp1["items"][0]["name"] == "count"

    resp2 = request_to_json("post", "/history/clear")
    assert resp2["success"] is True

    resp3 = request_to_json("get", "/history/get")
    assert resp3["items"] == []
def test_http_server_re_runs(re_manager, fastapi_server, suffix,
                             expected_n_items):  # noqa F811
    """
    Basic test for ``/re/run/...`` API. The API is tested on a single run plan.
    """
    resp1 = request_to_json("post", "/queue/item/add", json={"item": _plan3})
    assert resp1["success"] is True
    assert resp1["qsize"] == 1

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    request_to_json("post", "/queue/start")
    ttime.sleep(2)

    status = request_to_json("get", "/status")
    assert status["manager_state"] == "executing_queue"
    run_list_uid = status["run_list_uid"]
    assert isinstance(run_list_uid, str)

    req = "/re/runs" + suffix
    resp2 = request_to_json("get", req)
    assert resp2["success"] is True
    assert len(resp2["run_list"]) == expected_n_items
    assert resp2["run_list_uid"] == run_list_uid

    assert wait_for_manager_state_idle(30), "Timeout"
def test_http_server_queue_start_handler(re_manager,
                                         fastapi_server):  # noqa F811

    add_plans_to_queue()

    resp1 = request_to_json("post", "/queue/start")
    assert resp1 == {
        "success": False,
        "msg": "RE Worker environment does not exist."
    }

    resp2 = request_to_json("post", "/environment/open")
    assert resp2 == {"success": True, "msg": ""}
    resp2a = request_to_json("get", "/queue/get")
    assert len(resp2a["items"]) == 3
    assert resp2a["running_item"] == {}

    assert wait_for_environment_to_be_created(10), "Timeout"

    resp3 = request_to_json("post", "/queue/start")
    assert resp3 == {"success": True, "msg": ""}

    ttime.sleep(1)
    # The plan is currently being executed. 'get_queue' is expected to return currently executed plan.
    resp4 = request_to_json("get", "/queue/get")
    assert len(resp4["items"]) == 2
    assert resp4["running_item"][
        "name"] == "count"  # Check name of the running plan

    ttime.sleep(25)  # Wait until all plans are executed

    resp4 = request_to_json("get", "/queue/get")
    assert len(resp4["items"]) == 0
    assert resp2a["running_item"] == {}
def test_http_server_secure_1(monkeypatch, re_manager_cmd, fastapi_server_fs,
                              test_mode):  # noqa: F811
    """
    Test operation of HTTP server with enabled encryption. Security of HTTP server can be enabled
    only by setting the environment variable to the value of the public key.
    """
    public_key, private_key = generate_new_zmq_key_pair()

    if test_mode == "none":
        # No encryption
        pass
    elif test_mode == "ev":
        # Set server private key using environment variable
        monkeypatch.setenv("QSERVER_ZMQ_PRIVATE_KEY",
                           private_key)  # RE Manager
        monkeypatch.setenv("QSERVER_ZMQ_PUBLIC_KEY", public_key)  # HTTP server
        set_qserver_zmq_public_key(
            monkeypatch, server_public_key=public_key)  # For test functions
    else:
        raise RuntimeError(f"Unrecognized test mode '{test_mode}'")

    fastapi_server_fs()
    re_manager_cmd([])

    resp1 = request_to_json("post", "/queue/item/add", json={"item": _plan1})
    assert resp1["success"] is True, str(resp1)

    resp2 = request_to_json("post", "/queue/item/add", json={"item": _plan2})
    assert resp2["success"] is True, str(resp2)

    resp3 = request_to_json("get", "/plans/allowed")
    assert isinstance(resp3["plans_allowed"], dict)
    assert len(resp3["plans_allowed"]) > 0
    resp4 = request_to_json("get", "/devices/allowed")
    assert isinstance(resp4["devices_allowed"], dict)
    assert len(resp4["devices_allowed"]) > 0

    resp5 = request_to_json("post", "/environment/open")
    assert resp5["success"] is True
    assert wait_for_environment_to_be_created(10)

    resp6 = request_to_json("get", "/status")
    assert resp6["items_in_queue"] == 2
    assert resp6["items_in_history"] == 0

    resp7 = request_to_json("post", "/queue/start")
    assert resp7["success"] is True

    wait_for_queue_execution_to_complete(20)

    resp8 = request_to_json("get", "/status")
    assert resp8["items_in_queue"] == 0
    assert resp8["items_in_history"] == 2

    # Close the environment
    resp9 = request_to_json("post", "/environment/close")
    assert resp9 == {"success": True, "msg": ""}

    wait_for_manager_state_idle(10)
def test_http_server_re_pause_continue_handlers(
        re_manager,
        fastapi_server,
        option_pause,
        option_continue  # noqa F811
):
    resp1 = request_to_json("post", "/environment/open")
    assert resp1 == {"success": True, "msg": ""}

    assert wait_for_environment_to_be_created(10), "Timeout"

    resp2 = request_to_json(
        "post",
        "/queue/item/add",
        json={
            "item": {
                "name": "count",
                "args": [["det1", "det2"]],
                "kwargs": {
                    "num": 10,
                    "delay": 1
                },
                "item_type": "plan",
            }
        },
    )
    assert resp2["success"] is True
    assert resp2["qsize"] == 1
    assert resp2["item"]["name"] == "count"
    assert resp2["item"]["args"] == [["det1", "det2"]]
    assert "item_uid" in resp2["item"]

    resp3 = request_to_json("post", "/queue/start")
    assert resp3 == {"success": True, "msg": ""}
    ttime.sleep(
        3.5
    )  # Let some time pass before pausing the plan (fractional number of seconds)
    resp3a = request_to_json("post",
                             "/re/pause",
                             json={"option": option_pause})
    assert resp3a == {"msg": "", "success": True}
    ttime.sleep(2)  # TODO: API is needed
    resp3b = request_to_json("get", "/queue/get")
    assert len(
        resp3b["items"]) == 0  # The plan is paused, but it is not in the queue
    assert resp3b["running_item"] != {}  # Running plan is set

    resp4 = request_to_json("post", f"/re/{option_continue}")
    assert resp4 == {"msg": "", "success": True}

    ttime.sleep(15)  # TODO: we need to wait for plan completion

    resp4a = request_to_json("get", "/queue/get")
    # The plan returns to the queue if it is stopped
    assert len(resp4a["items"]) == 0 if option_continue == "resume" else 1
    assert resp4a["running_item"] == {}
def test_http_server_manager_stop_handler_1(re_manager, fastapi_server,
                                            option):  # noqa F811

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    kwargs = {"json": {"option": option} if option else {}}
    resp1 = request_to_json("post", "/manager/stop", **kwargs)
    assert resp1["success"] is True

    assert re_manager.check_if_stopped() is True
def test_http_server_open_environment_handler(re_manager,
                                              fastapi_server):  # noqa F811
    resp1 = request_to_json("post", "/environment/open")
    assert resp1 == {"success": True, "msg": ""}

    assert wait_for_environment_to_be_created(10), "Timeout"

    resp2 = request_to_json("post", "/environment/open")
    assert resp2 == {
        "success": False,
        "msg": "RE Worker environment already exists."
    }
def test_http_server_manager_kill(re_manager, fastapi_server):  # noqa F811

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    resp = request_to_json("post", "/test/manager/kill")
    assert resp["success"] is False
    assert "ZMQ communication error:" in resp["msg"]

    ttime.sleep(10)

    resp = request_to_json("get", "/status")
    assert resp["msg"] == "RE Manager"
    assert resp["manager_state"] == "idle"
    assert resp["items_in_queue"] == 0
    assert resp["running_item_uid"] is None
    assert resp["worker_environment_exists"] is True
def test_http_server_close_print_db_uids_handler(re_manager,
                                                 fastapi_server):  # noqa F811

    add_plans_to_queue()

    resp1 = request_to_json("post", "/environment/open")
    assert resp1 == {"success": True, "msg": ""}

    assert wait_for_environment_to_be_created(10), "Timeout"

    resp2 = request_to_json("post", "/queue/start")
    assert resp2 == {"success": True, "msg": ""}

    ttime.sleep(15)

    resp2a = request_to_json("get", "/queue/get")
    assert len(resp2a["items"]) == 0
    assert resp2a["running_item"] == {}
def test_http_server_queue_stop(re_manager, fastapi_server,
                                deactivate):  # noqa F811
    """
    Methods ``queue_stop_activate`` and ``queue_stop_deactivate``.
    """
    add_plans_to_queue()

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    # Queue is not running, so the request is expected to fail
    resp1 = request_to_json("post", "/queue/stop")
    assert resp1["success"] is False
    status = request_to_json("get", "/status")
    assert status["queue_stop_pending"] is False

    request_to_json("post", "/queue/start")
    ttime.sleep(2)
    status = request_to_json("get", "/status")
    assert status["manager_state"] == "executing_queue"

    resp2 = request_to_json("post", "/queue/stop")
    assert resp2["success"] is True
    status = request_to_json("get", "/status")
    assert status["queue_stop_pending"] is True

    if deactivate:
        ttime.sleep(1)

        resp3 = request_to_json("post", "/queue/stop/cancel")
        assert resp3["success"] is True
        status = request_to_json("get", "/status")
        assert status["queue_stop_pending"] is False

    ttime.sleep(15)

    status = request_to_json("get", "/status")
    assert status["manager_state"] == "idle"
    assert status["items_in_queue"] == (0 if deactivate else 2)
    assert status["items_in_history"] == (3 if deactivate else 1)
    assert status["running_item_uid"] is None
    assert status["worker_environment_exists"] is True
    assert status["queue_stop_pending"] is False
def test_http_server_close_environment_handler(re_manager,
                                               fastapi_server):  # noqa F811
    resp1 = request_to_json("post", "/environment/open")
    assert resp1 == {"success": True, "msg": ""}

    assert wait_for_environment_to_be_created(10), "Timeout"

    resp2 = request_to_json("post", "/environment/close")
    assert resp2 == {"success": True, "msg": ""}

    ttime.sleep(
        3
    )  # TODO: API needed to test if environment is closed. Use delay for now.

    resp3 = request_to_json("post", "/environment/close")
    assert resp3 == {
        "success": False,
        "msg": "RE Worker environment does not exist."
    }
def test_http_server_set_zmq_address_1(monkeypatch, re_manager_cmd,
                                       fastapi_server_fs):  # noqa: F811
    """
    Test if ZMQ address of RE Manager is passed to the HTTP server using 'QSERVER_ZMQ_ADDRESS' environment
    variable. Start RE Manager and HTTP server with ZMQ address for control communication channel
    different from default address, add and execute a plan.
    """

    # Change ZMQ address to use port 60616 instead of the default port 60615.
    zmq_server_address = "tcp://localhost:60616"
    monkeypatch.setenv("QSERVER_ZMQ_ADDRESS", zmq_server_address)  # RE Manager
    fastapi_server_fs()

    set_qserver_zmq_address(monkeypatch, zmq_server_address=zmq_server_address)
    re_manager_cmd(["--zmq-addr", "tcp://*:60616"])

    # Now execute a plan to make sure everything works as expected
    resp1 = request_to_json("post", "/queue/item/add", json={"item": _plan1})
    assert resp1["success"] is True, str(resp1)

    resp5 = request_to_json("post", "/environment/open")
    assert resp5["success"] is True
    assert wait_for_environment_to_be_created(10)

    resp6 = request_to_json("get", "/status")
    assert resp6["items_in_queue"] == 1
    assert resp6["items_in_history"] == 0

    resp7 = request_to_json("post", "/queue/start")
    assert resp7["success"] is True

    wait_for_queue_execution_to_complete(20)

    resp8 = request_to_json("get", "/status")
    assert resp8["items_in_queue"] == 0
    assert resp8["items_in_history"] == 1

    # Close the environment
    resp9 = request_to_json("post", "/environment/close")
    assert resp9 == {"success": True, "msg": ""}

    wait_for_manager_state_idle(10)
def test_http_server_manager_stop_handler_2(re_manager, fastapi_server,
                                            option):  # noqa F811

    add_plans_to_queue()

    request_to_json("post", "/environment/open")
    assert wait_for_environment_to_be_created(10), "Timeout"

    request_to_json("post", "/queue/start")

    ttime.sleep(2)
    resp = request_to_json("get", "/status")
    assert resp["msg"] == "RE Manager"
    assert resp["manager_state"] == "executing_queue"
    assert resp["items_in_queue"] == 2
    assert resp["running_item_uid"] is not None
    assert resp["items_in_history"] == 0
    assert resp["worker_environment_exists"] is True

    # Attempt to stop
    kwargs = {"json": {"option": option} if option else {}}
    resp1 = request_to_json("post", "/manager/stop", **kwargs)
    assert resp1["success"] == (option == "safe_off")

    if option == "safe_off":
        assert re_manager.check_if_stopped() is True

    else:
        # The queue is expected to be running
        ttime.sleep(15)
        resp = request_to_json("get", "/status")
        assert resp["msg"] == "RE Manager"
        assert resp["manager_state"] == "idle"
        assert resp["items_in_queue"] == 0
        assert resp["items_in_history"] == 3
        assert resp["running_item_uid"] is None
        assert resp["worker_environment_exists"] is True
def test_http_server_queue_item_get_remove_handler_3(
        re_manager, fastapi_server):  # noqa F811
    """
    Get and remove elements using plan UID. Successful and failing cases.
    Note: the test is derived from ZMQ API test ``test_zmq_api_queue_item_get_remove_3()``
    """
    request_to_json("post", "/queue/item/add", json={"item": _plan3})
    request_to_json("post", "/queue/item/add", json={"item": _plan2})
    request_to_json("post", "/queue/item/add", json={"item": _plan1})

    resp1 = request_to_json("get", "/queue/get")
    plans_in_queue = resp1["items"]
    assert len(plans_in_queue) == 3

    # Get and then remove plan 2 from the queue
    uid = plans_in_queue[1]["item_uid"]
    resp2a = request_to_json("post", "/queue/item/get", json={"uid": uid})
    assert resp2a["item"]["item_uid"] == plans_in_queue[1]["item_uid"]
    assert resp2a["item"]["name"] == plans_in_queue[1]["name"]
    assert resp2a["item"]["args"] == plans_in_queue[1]["args"]
    resp2b = request_to_json("post", "/queue/item/remove", json={"uid": uid})
    assert resp2b["item"]["item_uid"] == plans_in_queue[1]["item_uid"]
    assert resp2b["item"]["name"] == plans_in_queue[1]["name"]
    assert resp2b["item"]["args"] == plans_in_queue[1]["args"]

    # Start the first plan (this removes it from the queue)
    #   Also the rest of the operations will be performed on a running queue.
    resp3 = request_to_json("post", "/environment/open")
    assert resp3["success"] is True
    assert wait_for_environment_to_be_created(10)

    resp4 = request_to_json("post", "/queue/start")
    assert resp4["success"] is True

    ttime.sleep(1)
    uid = plans_in_queue[0]["item_uid"]
    resp5a = request_to_json("post", "/queue/item/get", json={"uid": uid})
    assert resp5a["success"] is False
    assert "is currently running" in resp5a["msg"]
    resp5b = request_to_json("post", "/queue/item/remove", json={"uid": uid})
    assert resp5b["success"] is False
    assert "Can not remove an item which is currently running" in resp5b["msg"]

    uid = "nonexistent"
    resp6a = request_to_json("post", "/queue/item/get", json={"uid": uid})
    assert resp6a["success"] is False
    assert "not in the queue" in resp6a["msg"]
    resp6b = request_to_json("post", "/queue/item/remove", json={"uid": uid})
    assert resp6b["success"] is False
    assert "not in the queue" in resp6b["msg"]

    # Remove the last entry
    uid = plans_in_queue[2]["item_uid"]
    resp7a = request_to_json("post", "/queue/item/get", json={"uid": uid})
    assert resp7a["success"] is True
    resp7b = request_to_json("post", "/queue/item/remove", json={"uid": uid})
    assert resp7b["success"] is True

    ttime.sleep(10)  # TODO: wait for the queue processing to be completed

    state = request_to_json("get", "/status")
    assert state["items_in_queue"] == 0
    assert state["items_in_history"] == 1
def test_http_server_queue_upload_spreasheet_1(re_manager, fastapi_server_fs,
                                               tmp_path,
                                               monkeypatch):  # noqa F811
    """
    Test for ``/queue/upload/spreadsheet`` API: generate .xlsx file, upload it to the server, verify
    the contents of the queue, run the queue and verify that the required number of plans were successfully
    completed.
    """
    monkeypatch.setenv(
        "BLUESKY_HTTPSERVER_CUSTOM_MODULE",
        "bluesky_queueserver.server.tests.http_custom_proc_functions",
        prepend=False,
    )
    fastapi_server_fs()

    plan_params = [["count", 5, 1], ["count", 6, 0.5]]
    col_names = ["name", "num", "delay"]
    ss_path, plans_expected = _create_test_excel_file1(tmp_path,
                                                       plan_params=plan_params,
                                                       col_names=col_names)

    # Send the Excel file to the server
    files = {"spreadsheet": open(ss_path, "rb")}
    resp1 = request_to_json("post", "/queue/upload/spreadsheet", files=files)
    assert resp1["success"] is True, str(resp1)
    items1 = resp1["items"]
    results1 = resp1["results"]
    assert len(items1) == len(plans_expected), str(items1)
    for p, p_exp in zip(items1, plans_expected):
        for k, v in p_exp.items():
            assert k in p
            assert v == p[k]

    assert len(results1) == len(plans_expected), str(results1)
    assert all([_["success"] is True for _ in results1]), str(results1)
    assert all([_["msg"] == "" for _ in results1]), str(results1)

    # Verify that the queue contains correct plans
    resp2 = request_to_json("get", "/queue/get")
    assert resp2["success"] is True
    assert resp2["running_item"] == {}
    queue = resp2["items"]
    assert len(queue) == len(plans_expected), str(queue)
    for p, p_exp in zip(queue, plans_expected):
        for k, v in p_exp.items():
            assert k in p
            assert v == p[k]

    resp3 = request_to_json("post", "/environment/open")
    assert resp3["success"] is True
    assert wait_for_environment_to_be_created(10)

    resp4 = request_to_json("post", "/queue/start")
    assert resp4["success"] is True
    assert wait_for_queue_execution_to_complete(60)

    resp5 = request_to_json("get", "/status")
    assert resp5["items_in_queue"] == 0
    assert resp5["items_in_history"] == len(plans_expected)

    resp6 = request_to_json("post", "/environment/close")
    assert resp6 == {"success": True, "msg": ""}
    assert wait_for_manager_state_idle(10)
def test_http_server_queue_upload_spreasheet_4(
        re_manager,
        fastapi_server_fs,
        tmp_path,
        monkeypatch,
        use_custom  # noqa F811
):
    """
    Test for ``/queue/upload/spreadsheet`` API. Pass the spreadsheet to the default processing function
    either directly (use_custom=False) or first pass it to the custom processing function which
    rejects the spreadsheet by returning ``None``. If custom processing function returns ``None``, then
    the spreadsheet is passed to the default function.

    NOTE: currently the default processing function is not implemented and the request returns error message.
    The test will have to be modified, when the function is implemented.
    """
    if use_custom:
        monkeypatch.setenv(
            "BLUESKY_HTTPSERVER_CUSTOM_MODULE",
            "bluesky_queueserver.server.tests.http_custom_proc_functions",
            prepend=False,
        )
    fastapi_server_fs()

    ss_path = create_excel_file_from_plan_list(tmp_path,
                                               plan_list=plan_list_sample)
    plans_expected = [
        _ for _ in plan_list_sample if isinstance(_["name"], str)
    ]

    # Send the Excel file to the server
    params = {"files": {"spreadsheet": open(ss_path, "rb")}}
    if use_custom:
        params["data"] = {"data_type": "process_with_default_function"}
    resp1 = request_to_json("post", "/queue/upload/spreadsheet", **params)
    assert resp1["success"] is True, str(resp1)
    assert "items" in resp1, str(resp1)
    assert "results" in resp1, str(resp1)
    assert len(resp1["results"]) == len(plans_expected), str(resp1)

    # Verify that the queue contains correct plans
    resp2 = request_to_json("get", "/queue/get")
    assert resp2["success"] is True
    assert resp2["running_item"] == {}
    queue = resp2["items"]
    assert len(queue) == len(plans_expected), str(queue)
    for p, p_exp in zip(queue, plans_expected):
        for k, v in p_exp.items():
            assert k in p
            assert v == p[k]

    resp3 = request_to_json("post", "/environment/open")
    assert resp3["success"] is True
    assert wait_for_environment_to_be_created(10)

    resp4 = request_to_json("post", "/queue/start")
    assert resp4["success"] is True
    assert wait_for_queue_execution_to_complete(60)

    resp5 = request_to_json("get", "/status")
    assert resp5["items_in_queue"] == 0
    assert resp5["items_in_history"] == len(plans_expected)

    resp6 = request_to_json("post", "/environment/close")
    assert resp6 == {"success": True, "msg": ""}
    assert wait_for_manager_state_idle(10)