async def test_receive_message_for_subscribed_group_only(self):
        """Test that clients subscribed to some groups only receive messages from those."""
        # Arrange
        communicator = WebsocketCommunicator(application, self.url)
        connected, subprotocol = await communicator.connect()
        subscription_msg = {
            'option': 'subscribe',
            'category': 'telemetry',
            'csc': 'ScriptQueue',
            'salindex': 1,
            'stream': 'stream1',
        }
        await communicator.send_json_to(subscription_msg)
        await communicator.receive_json_from()
        # Act
        for combination in self.combinations:
            msg, expected = \
                self.build_messages(combination['category'], combination['csc'], combination['salindex'], [
                                    combination['stream']])
            await communicator.send_json_to(msg)

            if combination['category'] == subscription_msg['category'] and \
                    combination['csc'] == subscription_msg['csc'] and \
                    combination['salindex'] == subscription_msg['salindex'] and \
                    combination['stream'] == subscription_msg['stream']:
                response = await communicator.receive_json_from()
                # Assert
                assert response == expected
            else:
                # Assert
                with pytest.raises(asyncio.TimeoutError):
                    await asyncio.wait_for(communicator.receive_json_from(),
                                           timeout=self.no_reception_timeout)
        await communicator.disconnect()
    async def test_receive_part_of_message_for_subscribed_groups_only(self):
        """Test that clients subscribed to some groups only receive the corresponding part of incoming messages."""
        # Arrange
        communicator = WebsocketCommunicator(application, self.url)
        connected, subprotocol = await communicator.connect()
        for combination in self.combinations:
            # Arrange: Subscribe to 1
            subscription_msg = {
                "csc": combination["csc"],
                "stream": combination["stream"],
                "category": combination["category"],
                "salindex": combination["salindex"],
            }
            subscription_msg["option"] = "subscribe"
            await communicator.send_json_to(subscription_msg)
            await communicator.receive_json_from()
            # Act: Send the big message
            msg, ignore = self.build_messages(
                combination["category"],
                combination["csc"],
                combination["salindex"],
                self.streams,
            )
            await communicator.send_json_to(msg)

            if (
                combination["category"] == subscription_msg["category"]
                and combination["csc"] == subscription_msg["csc"]
                and combination["salindex"] == subscription_msg["salindex"]
                and combination["stream"] == subscription_msg["stream"]
            ):
                response = await communicator.receive_json_from()
                # Assert: receive the one subscribed to
                ignore, expected = self.build_messages(
                    combination["category"],
                    combination["csc"],
                    combination["salindex"],
                    [combination["stream"]],
                )
                assert response == expected
            else:
                # Assert: not receive all the others
                with pytest.raises(asyncio.TimeoutError):
                    await asyncio.wait_for(
                        communicator.receive_json_from(),
                        timeout=self.no_reception_timeout,
                    )
            # Clean: Unsubscribe from 1
            subscription_msg["option"] = "unsubscribe"
            await communicator.send_json_to(subscription_msg)
            await communicator.receive_json_from()
        await communicator.disconnect()
    async def test_receive_message_for_subscribed_group_only(self):
        """Test that clients subscribed to some groups only receive messages from those."""
        # Arrange
        communicator = WebsocketCommunicator(application, self.url)
        connected, subprotocol = await communicator.connect()
        subscription_msg = {
            "option": "subscribe",
            "category": "telemetry",
            "csc": "ScriptQueue",
            "salindex": 1,
            "stream": "stream1",
        }
        await communicator.send_json_to(subscription_msg)
        await communicator.receive_json_from()
        # Act
        for combination in self.combinations:
            msg, expected = self.build_messages(
                combination["category"],
                combination["csc"],
                combination["salindex"],
                [combination["stream"]],
            )
            await communicator.send_json_to(msg)

            if (
                combination["category"] == subscription_msg["category"]
                and combination["csc"] == subscription_msg["csc"]
                and combination["salindex"] == subscription_msg["salindex"]
                and combination["stream"] == subscription_msg["stream"]
            ):
                response = await communicator.receive_json_from()
                # Assert
                assert response == expected
            else:
                # Assert
                with pytest.raises(asyncio.TimeoutError):
                    await asyncio.wait_for(
                        communicator.receive_json_from(),
                        timeout=self.no_reception_timeout,
                    )
        await communicator.disconnect()
    async def test_receive_message_for_subscribed_groups_only(self):
        """Test that clients subscribed to some groups only receive messages from those."""
        # Arrange
        communicator = WebsocketCommunicator(application, self.url)
        connected, subprotocol = await communicator.connect()
        for combination in self.combinations:
            # Arrange: Subscribe to 1
            subscription_msg = {
                "csc": combination["csc"],
                "stream": combination["stream"],
                "category": combination["category"],
                "salindex": combination["salindex"]
            }
            subscription_msg['option'] = 'subscribe'
            await communicator.send_json_to(subscription_msg)
            await communicator.receive_json_from()
            # Act: Send and receive all
            for combination in self.combinations:
                msg, expected = \
                    self.build_messages(combination['category'], combination['csc'], combination['salindex'], [
                                        combination['stream']])
                await communicator.send_json_to(msg)

                if combination['category'] == subscription_msg['category'] and \
                        combination['csc'] == subscription_msg['csc'] and \
                        combination['salindex'] == subscription_msg['salindex'] and \
                        combination['stream'] == subscription_msg['stream']:
                    response = await communicator.receive_json_from()
                    # Assert: receive the one subscribed to
                    assert response == expected
                else:
                    # Assert: not receive all the others
                    with pytest.raises(asyncio.TimeoutError):
                        await asyncio.wait_for(
                            communicator.receive_json_from(),
                            timeout=self.no_reception_timeout)
            # Clean: Unsubscribe from 1
            subscription_msg['option'] = 'unsubscribe'
            await communicator.send_json_to(subscription_msg)
            await communicator.receive_json_from()
        await communicator.disconnect()
    async def test_livechat_flow(self, settings, client: APIClient,
                                 populate_db: pytest.fixture) -> None:
        # settings.CHANNEL_LAYERS = TEST_CHANNEL_LAYERS
        application = ProtocolTypeRouter({
            'websocket':
            AuthMiddlewareStack(
                SessionMiddlewareStack(
                    URLRouter([
                        re_path(r'ws/chat/(?P<room_name>\w+)/$',
                                TemplateChatConsumer)
                    ])))
        })

        # Create a dummy admin user
        admin_user = mixer.blend(User, role='AO')

        # Login as admin operator
        admin = APIClient()
        admin.login(username=admin_user, password='******')
        user = auth.get_user(admin)  # type: ignore
        assert user.is_authenticated

        _, bots, variables = populate_db
        for (bot_id, variable) in zip(bots, variables):
            # Start a new session for the template chat
            response = client.get(f'/api/clientwidget/session/{bot_id}')
            assert response.status_code == 200

            response_data = json.loads(response.content)
            assert response_data["nodeType"] == "INIT" and response_data[
                "room_name"] is not None and response_data[
                    "variables"] == variable

            room_name = response_data["room_name"]

            # Create the test room in ChatRoom
            room_instance, _ = ChatRoom.objects.get_or_create(
                room_name=room_name, bot_id=bot_id)

            assert room_instance.room_name == room_name

            # Create 2 communicators - One for the client and one for the admin
            client_socket = WebsocketCommunicator(application,
                                                  f"/ws/chat/{room_name}/")
            admin_socket = WebsocketCommunicator(application,
                                                 f"/ws/chat/{room_name}/")

            admin_socket.scope["user"] = admin_user

            client_connected, _ = await client_socket.connect()

            assert client_connected == True

            admin_connected = None

            user_inputs = []  # List of variable inputs per bot session
            variable_dict = room_instance.variables  # And the corresponding dictionary from the room instance

            is_livechat = False

            while not ("nodeType" in response_data
                       and response_data["nodeType"] == "END"):
                # Wait for 2 seconds
                time.sleep(2)

                if admin_connected == True:
                    # Switch to Livechat
                    is_livechat = True
                    break

                # The message to be sent
                message = dict()
                payload = dict()
                # Set some flags
                yes_no, multi_choice = False, False
                if "nodeType" in response_data and response_data[
                        "nodeType"] == "YES_NO":
                    # If YES / NO button
                    yes_no = True
                    choice = random.choice(["Yes", "No"])
                    for node in response_data["buttons"]:
                        if node["text"] == choice:
                            payload['target_id'] = node['targetId']
                            payload['post_data'] = choice
                elif "nodeType" in response_data and response_data[
                        "nodeType"] == "MULTI_CHOICE":
                    # If Multi choice button
                    multi_choice = True
                    choices = set()
                    for node in response_data["buttons"]:
                        choices.add(node["text"])
                    payload['target_id'] = node['targetId']
                    payload['post_data'] = random.choice(tuple(choices))
                elif "nodeType" in response_data and response_data[
                        "nodeType"] == "INIT":
                    # Try to go to the next target_id node
                    payload['target_id'] = response_data["targetId"]
                else:
                    # Try to go to the next target_id node
                    payload['target_id'] = response_data["targetId"]

                if "variable" in response_data:
                    payload["variable"] = response_data["variable"]
                    if multi_choice:
                        # Multi choice variable already set
                        user_inputs.append(payload["post_data"])
                        variable_dict[
                            response_data['variable']] = payload["post_data"]
                    elif yes_no:
                        # Yes / No variable already set
                        user_inputs.append(payload["post_data"])
                        variable_dict[
                            response_data['variable']] = payload["post_data"]
                    else:
                        # Give a random input
                        length = 10
                        payload["post_data"] = ''.join(
                            random.choice(string.ascii_letters)
                            for i in range(length))
                        user_inputs.append(payload["post_data"])
                        variable_dict[
                            response_data['variable']] = payload["post_data"]

                # Set the bot parameters
                message["user"] = "******"
                message["bot_id"] = str(bot_id)
                message["data"] = payload  # type: ignore

                await client_socket.send_json_to(message)
                response_data = await client_socket.receive_json_from(timeout=3
                                                                      )

                # If the bot is still active
                if response_data["user"] == "bot":
                    if 'data' in response_data:
                        response_data = response_data['data']
                    else:
                        break
                else:
                    break

                if (yes_no == True
                        or multi_choice == True) and admin_connected is None:
                    # Make the admin enter at this point
                    admin_connected, _ = await admin_socket.connect()
                    assert admin_connected == True

            while is_livechat == True:
                # Send from admin communicator
                await admin_socket.send_json_to({
                    "user": "******",
                    "message": "Hello from admin"
                })

                futures = await asyncio.gather(
                    client_socket.receive_json_from(),
                    admin_socket.receive_json_from(),
                )
                response_data_client, response_data_admin = futures

                assert response_data_client == response_data_admin
                assert response_data_admin[
                    "user"] == "admin" and response_data_admin[
                        "message"] == "Hello from admin"  # type: ignore

                # Now send from client communicator
                await client_socket.send_json_to({
                    "user": "******",
                    "message": {
                        "userInputVal": "Hello from client"
                    }
                })

                futures = await asyncio.gather(
                    client_socket.receive_json_from(),
                    admin_socket.receive_json_from(),
                )
                response_data_client, response_data_admin = futures

                assert response_data_client == response_data_admin
                assert response_data_client[
                    "user"] == "end_user" and response_data_client["message"][
                        "userInputVal"] == "Hello from client"  # type: ignore

                # Now exit
                break

            # Verify session variables
            response = admin.get(
                f"/api/clientwidget/{room_name}/sessionvariables")
            assert response.status_code == 200
            session_variables = json.loads(response.content)
            assert session_variables == variable_dict

            # Variables in DB must be empty
            response = admin.get(f"/api/clientwidget/{room_name}/variables")
            assert response.status_code == 200
            variables = json.loads(response.content)
            assert variables == {key: "" for key in variable_dict}

            # Now verify the chat session history
            response = admin.get(
                f"/api/clientwidget/{room_name}/sessionhistory")
            assert response.status_code == 200

            session_history = json.loads(response.content)
            assert len(session_history) == 2
            assert session_history == [{
                "user": "******",
                "message": "Hello from admin"
            }, {
                "user": "******",
                "message": {
                    "userInputVal": "Hello from client"
                }
            }]

            # DB History must be empty
            response = admin.get(f"/api/clientwidget/{room_name}/history")
            assert response.status_code == 400 or response.status_code == 200
            if response.status_code == 200:
                # List must be empty
                history = json.loads(response.content)
                assert len(history) == 0

            # Disconnect
            await client_socket.disconnect()
            await admin_socket.disconnect()

            # Now session history must be empty
            response = admin.get(
                f"/api/clientwidget/{room_name}/sessionhistory")
            assert response.status_code == 400 or response.status_code == 200
            if response.status_code == 200:
                # List must be empty
                flushed_history = json.loads(response.content)
                assert len(flushed_history) == 0

            # While DB history is not
            response = admin.get(f"/api/clientwidget/{room_name}/history")
            assert response.status_code == 200

            history = json.loads(response.content)
            assert history == session_history

            # Variables in session must be empty
            response = admin.get(
                f"/api/clientwidget/{room_name}/sessionvariables")
            assert response.status_code == 404

            # While it must be updated in DB
            response = admin.get(f"/api/clientwidget/{room_name}/variables")
            assert response.status_code == 200
            variables = json.loads(response.content)
            assert variables == session_variables