def test_websocket_process_detail_with_abort(test_client, test_workflow): if websocket_manager.broadcaster_type != "memory": pytest.skip("test does not work with redis") response = test_client.post(f"/api/processes/{test_workflow}", json=[{}]) assert ( HTTPStatus.CREATED == response.status_code ), f"Invalid response status code (response data: {response.json()})" pid = response.json()["id"] try: with test_client.websocket_connect( "api/processes/all?token=") as websocket: # Abort process response = test_client.put(f"/api/processes/{pid}/abort") assert HTTPStatus.NO_CONTENT == response.status_code data = websocket.receive_text() process = json_loads(data)["process"] assert process["status"] == ProcessStatus.SUSPENDED assert process["assignee"] == Assignee.CHANGES data = websocket.receive_text() process = json_loads(data)["process"] assert process["status"] == ProcessStatus.ABORTED assert process["assignee"] == Assignee.SYSTEM # close and call receive_text to check websocket close exception websocket.close() data = websocket.receive_text() except WebSocketDisconnect as exception: assert exception.code == status.WS_1000_NORMAL_CLOSURE
def test_deserialization_datetime(): json_str = '{"end_date": "2019-12-06T19:25:22+00:00"}' dct = json_loads(json_str) assert "end_date" in dct assert dct["end_date"] == datetime(2019, 12, 6, 19, 25, 22, 0, timezone.utc) dct = {"end_date": datetime(2019, 12, 6, 19, 25, 22, 0, timezone.utc)} assert json_loads(json_dumps(dct)) == dct
def test_subscription_detail_with_domain_model_cache(test_client, generic_subscription_1): # test with a subscription that has domain model and without subscription = SubscriptionModel.from_subscription(generic_subscription_1) extended_model = build_extendend_domain_model(subscription) etag = _generate_etag(extended_model) app_settings.CACHE_DOMAIN_MODELS = True to_redis(extended_model) response = test_client.get( URL("api/subscriptions/domain-model") / generic_subscription_1) cache = Redis(host=app_settings.CACHE_HOST, port=app_settings.CACHE_PORT) result = cache.get(f"domain:{generic_subscription_1}") cached_model = json_dumps(json_loads(result)) cached_etag = cache.get(f"domain:etag:{generic_subscription_1}") assert cached_model == json_dumps(extended_model) assert cached_etag.decode("utf-8") == etag assert response.status_code == HTTPStatus.OK assert response.json()["subscription_id"] == generic_subscription_1 app_settings.CACHE_DOMAIN_MODELS = False cache.delete(f"domain:{generic_subscription_1}")
def test_accept_ok(): class Form(FormPage): accept: Accept validated_data = Form(accept="ACCEPTED").dict() expected = {"accept": True} assert expected == json_loads(json_dumps(validated_data))
async def sender(self, websocket: WebSocket, channel: str) -> None: async with self.sub_broadcast.subscribe(channel=channel) as subscriber: async for event in subscriber: await websocket.send_text(event.message) json = json_loads(event.message) if type(json) is dict and "close" in json and json["close"] and channel != "processes": await self.disconnect(websocket) break
def test_post_process_yield(): def input_form(state): user_input = yield TestForm return {**user_input.dict(), "extra": 234} validated_data = post_process(input_form, {"previous": True}, [{ "generic_select": "a" }]) expected = {"generic_select": "a", "extra": 234} assert expected == json_loads(json_dumps(validated_data))
async def form_error_handler(request: Request, exc: FormException) -> JSONResponse: if isinstance(exc, FormValidationError): return JSONResponse( { "type": type(exc).__name__, "detail": str(exc), "traceback": show_ex(exc), "title": "Form not valid", # We need to make sure the is nothing the default json.dumps cannot handle "validation_errors": json_loads(json_dumps(exc.errors)), "status": HTTPStatus.BAD_REQUEST, }, status_code=HTTPStatus.BAD_REQUEST, ) elif isinstance(exc, FormNotCompleteError): return JSONResponse( { "type": type(exc).__name__, "detail": str(exc), "traceback": show_ex(exc), # We need to make sure the is nothing the default json.dumps cannot handle "form": json_loads(json_dumps(exc.form)), "title": "Form not complete", "status": HTTPStatus.NOT_EXTENDED, }, status_code=HTTPStatus.NOT_EXTENDED, ) else: return JSONResponse( { "detail": str(exc), "title": "Internal Server Error", "status": HTTPStatus.INTERNAL_SERVER_ERROR, "type": type(exc).__name__, }, status_code=HTTPStatus.INTERNAL_SERVER_ERROR, )
def from_redis(subscription_id: UUID) -> Optional[Tuple[Any, str]]: if caching_models_enabled(): logger.info("Retrieving subscription from cache", subscription=subscription_id) obj = cache.get(f"domain:{subscription_id}") etag = cache.get(f"domain:etag:{subscription_id}") if obj and etag: return json_loads(obj), etag.decode("utf-8") # type: ignore else: return None else: logger.warning("Caching disabled, not loading subscription", subscription=subscription_id) return None
def log_user_info(user_name: str, message: Dict = Body(...)) -> Dict: """Log frontend messages that are related to user actions. When the frontend finalizes the setup of a login session it will do a HTTP POST to this endpoint. The frontend will also post to this endpoint when it ends a user session. Args: user_name: the username (email) of the user involved message: A log message. Returns: {} """ try: json_dict: Any = json_loads(str(message)) _message = json_dict["message"] except Exception: _message = message logger.info("Client sent user info", message=_message, user_name=user_name) return {}
def test_websocket_process_list_multiple_workflows(test_client, test_workflow, test_workflow_2): # This tests the ProcessDataBroadcastThread as well if websocket_manager.broadcaster_type != "memory": pytest.skip("test does not work with redis") # to keep track of the amount of websocket messages message_count = 0 expected_workflow_1_steps = [ { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Start", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Insert UUID in state", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Test that it is a string now", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.CHANGES, "status": ProcessStatus.SUSPENDED, "step": "Modify", "step_status": StepStatus.SUSPEND, }, ] expected_workflow_2_steps = [ { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Start", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Insert UUID in state", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "Test that it is a string now", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.RUNNING, "step": "immediate step", "step_status": StepStatus.SUCCESS, }, { "assignee": Assignee.SYSTEM, "status": ProcessStatus.COMPLETED, "step": "Done", "step_status": StepStatus.COMPLETE, }, ] test_workflow_1_messages = [] test_workflow_2_messages = [] try: with test_client.websocket_connect( "api/processes/all/?token=") as websocket: # start test_workflow test_workflow_response = test_client.post( f"/api/processes/{test_workflow}", json=[{}]) assert ( HTTPStatus.CREATED == test_workflow_response.status_code ), f"Invalid response status code (response data: {test_workflow_response.json()})" test_workflow_1_pid = test_workflow_response.json()["id"] # Start test_workflow_2 response = test_client.post(f"/api/processes/{test_workflow_2}", json=[{}]) assert ( HTTPStatus.CREATED == response.status_code ), f"Invalid response status code (response data: {response.json()})" test_workflow_2_pid = response.json()["id"] # Make sure it started again time.sleep(1) # close and call receive_text to check websocket close exception websocket.close() # Checks if the correct messages are send, without order for which workflow. while True: if message_count == 9: break message = websocket.receive_text() message_count += 1 json_data = json_loads(message) assert "process" in json_data, f"Websocket message does not contain process: {json_data}" process_id = json_data["process"]["id"] if process_id == test_workflow_1_pid: test_workflow_1_messages.append(json_data) elif process_id == test_workflow_2_pid: test_workflow_2_messages.append(json_data) except WebSocketDisconnect as exception: assert exception.code == status.WS_1000_NORMAL_CLOSURE except AssertionError as e: raise e assert message_count == 9 for index, message in enumerate(test_workflow_1_messages): process = message["process"] expectedData = expected_workflow_1_steps.pop(0) assert process["assignee"] == expectedData["assignee"] assert process["status"] == expectedData["status"] assert process["step"] == expectedData["step"] assert process["steps"][index]["name"] == expectedData["step"] assert process["steps"][index]["status"] == expectedData["step_status"] for index, message in enumerate(test_workflow_2_messages): process = message["process"] expectedData = expected_workflow_2_steps.pop(0) assert process["assignee"] == expectedData["assignee"] assert process["status"] == expectedData["status"] assert process["step"] == expectedData["step"] assert process["steps"][index]["name"] == expectedData["step"] assert process["steps"][index]["status"] == expectedData["step_status"]
def test_websocket_process_detail_workflow(test_client, long_running_workflow): if websocket_manager.broadcaster_type != "memory" or app_settings.ENVIRONMENT != "local": pytest.skip("test does not work with redis") app_settings.TESTING = False # Start the workflow response = test_client.post(f"/api/processes/{long_running_workflow}", json=[{}]) assert ( HTTPStatus.CREATED == response.status_code ), f"Invalid response status code (response data: {response.json()})" pid = response.json()["id"] response = test_client.get(f"api/processes/{pid}") assert HTTPStatus.OK == response.status_code # Make sure it started again time.sleep(1) try: with test_client.websocket_connect( "api/processes/all?token=") as websocket: # Check the websocket messages. # the initial process details. data = websocket.receive_text() process = json_loads(data)["process"] assert process["workflow_name"] == "long_running_workflow_py" assert process["status"] == ProcessStatus.RUNNING # Let first long step finish, receive_text would otherwise wait for a message indefinitely. with test_condition: test_condition.notify_all() time.sleep(1) # message step 1. data = websocket.receive_text() json_data = json_loads(data) process = json_data["process"] assert process["status"] == ProcessStatus.RUNNING assert process["steps"][1]["name"] == LONG_RUNNING_STEP assert process["steps"][1]["status"] == StepStatus.SUCCESS # message step 2. data = websocket.receive_text() json_data = json_loads(data) process = json_data["process"] assert process["status"] == ProcessStatus.RUNNING assert process["steps"][2]["name"] == IMMEDIATE_STEP assert process["steps"][2]["status"] == StepStatus.SUCCESS # Let second long step finish, receive_text would otherwise wait for a message indefinitely. with test_condition: test_condition.notify_all() time.sleep(1) # message step 3. data = websocket.receive_text() json_data = json_loads(data) process = json_data["process"] assert process["status"] == ProcessStatus.RUNNING assert process["steps"][3]["name"] == LONG_RUNNING_STEP assert process["steps"][3]["status"] == StepStatus.SUCCESS # message step 4. data = websocket.receive_text() json_data = json_loads(data) process = json_data["process"] assert process["status"] == ProcessStatus.COMPLETED assert process["steps"][4]["name"] == "Done" assert process["steps"][4]["status"] == StepStatus.COMPLETE assert json_data["close"] is True # close and call receive_text to check websocket close exception websocket.close() data = websocket.receive_text() except WebSocketDisconnect as exception: assert exception.code == status.WS_1000_NORMAL_CLOSURE except AssertionError as e: # Finish steps so the test doesn't get stuck waiting forever. with test_condition: test_condition.notify_all() with test_condition: test_condition.notify_all() app_settings.TESTING = True raise e with test_condition: test_condition.notify_all() app_settings.TESTING = True
def test_post_process_wizard(): # Return if there is no form assert post_process(None, {}, []) == {} assert post_process([], {}, []) == {} def input_form(state): class TestForm1(FormPage): generic_select1: TestChoices class Config: title = "Some title" class TestForm2(FormPage): generic_select2: TestChoices class TestForm3(FormPage): generic_select3: TestChoices user_input_1 = yield TestForm1 if user_input_1.generic_select1 == TestChoices.A: user_input_2 = yield TestForm2 else: user_input_2 = yield TestForm3 return {**user_input_1.dict(), **user_input_2.dict()} # Submit 1 with pytest.raises(FormNotCompleteError) as error_info: post_process(input_form, {"previous": True}, []) assert error_info.value.form == { "title": "Some title", "type": "object", "additionalProperties": False, "definitions": { "TestChoices": { "description": "An enumeration.", "enum": ["a", "b"], "title": "TestChoices", "type": "string", } }, "properties": { "generic_select1": { "$ref": "#/definitions/TestChoices" } }, "required": ["generic_select1"], } # Submit 2 with pytest.raises(FormNotCompleteError) as error_info: post_process(input_form, {"previous": True}, [{ "generic_select1": "b" }]) assert error_info.value.form == { "title": "unknown", "type": "object", "additionalProperties": False, "definitions": { "TestChoices": { "description": "An enumeration.", "enum": ["a", "b"], "title": "TestChoices", "type": "string", } }, "properties": { "generic_select3": { "$ref": "#/definitions/TestChoices" } }, "required": ["generic_select3"], } # Submit complete validated_data = post_process(input_form, {"previous": True}, [{ "generic_select1": "b" }, { "generic_select3": "a" }]) expected = {"generic_select1": "b", "generic_select3": "a"} assert expected == json_loads(json_dumps(validated_data)) # Submit overcomplete validated_data = post_process(input_form, {"previous": True}, [{ "generic_select1": "b" }, { "generic_select3": "a" }, { "to_much": True }]) expected = {"generic_select1": "b", "generic_select3": "a"} assert expected == json_loads(json_dumps(validated_data))