コード例 #1
0
async def test_connection_error():
    """Test that server responds correctly when connection errors happen.

    Check that server responds with message of type `connection_error`
    when there was an exception in `on_connect` method.
    """

    print("Prepare application.")

    class MyGraphqlWsConsumerConnectionError(GraphqlWsConsumer):
        """Channels WebSocket consumer which provides GraphQL API."""

        schema = ""

        async def on_connect(self, payload):
            from graphql.error import GraphQLError

            # Always close the connection.
            raise GraphQLError("Reject connection")

    application = channels.routing.ProtocolTypeRouter(
        {
            "websocket": channels.routing.URLRouter(
                [
                    django.urls.path(
                        "graphql-connection-error/", MyGraphqlWsConsumerConnectionError
                    )
                ]
            )
        }
    )

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(
        application=application,
        path="graphql-connection-error/",
        subprotocols=["graphql-ws"],
    )

    print("Try to initialize the connection.")
    await comm.connect(timeout=TIMEOUT)
    await comm.send_json_to({"type": "connection_init", "payload": ""})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["type"] == "connection_error"
    assert resp["payload"]["message"] == "Reject connection"
    resp = await comm.receive_output(timeout=TIMEOUT)
    assert resp["type"] == "websocket.close"
    assert resp["code"] == 4000

    print("Disconnect and wait the application to finish gracefully.")
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #2
0
async def test_keepalive():
    """Test that server sends keepalive messages."""

    print("Prepare application.")

    class MyGraphqlWsConsumerKeepalive(GraphqlWsConsumer):
        """Channels WebSocket consumer which provides GraphQL API."""

        schema = ""
        # Period to send keepalive mesages. Just some reasonable number.
        send_keepalive_every = 0.05

    application = channels.routing.ProtocolTypeRouter(
        {
            "websocket": channels.routing.URLRouter(
                [django.urls.path("graphql-keepalive/", MyGraphqlWsConsumerKeepalive)]
            )
        }
    )

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(
        application=application, path="graphql-keepalive/", subprotocols=["graphql-ws"]
    )

    print("Establish & initialize the connection.")
    await comm.connect(timeout=TIMEOUT)
    await comm.send_json_to({"type": "connection_init", "payload": ""})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["type"] == "connection_ack"
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert (
        resp["type"] == "ka"
    ), "Keepalive message expected right after `connection_ack`!"

    print("Receive several keepalive messages.")
    pings = []
    for _ in range(3):
        pings.append(await comm.receive_json_from(timeout=TIMEOUT))
    assert all([ping["type"] == "ka" for ping in pings])

    print("Send connection termination message.")
    await comm.send_json_to({"type": "connection_terminate"})

    print("Disconnect and wait the application to finish gracefully.")
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #3
0
    async def create_and_subscribe(userId):
        """Establish and initialize WebSocket GraphQL connection.

        Subscribe to GraphQL subscription by userId.

        Args:
            userId: User ID for `on_chat_message_sent` subscription.

        Returns:
            sub_id: Subscription uid.
            comm: Client, instance of the `WebsocketCommunicator`.
        """
        comm = ch_testing.WebsocketCommunicator(
            application=my_app, path="graphql/", subprotocols=["graphql-ws"]
        )

        await comm.connect(timeout=TIMEOUT)
        await comm.send_json_to({"type": "connection_init", "payload": ""})
        await comm.receive_json_from(timeout=TIMEOUT)

        sub_id = str(uuid.uuid4().hex)
        await comm.send_json_to(
            {
                "id": sub_id,
                "type": "start",
                "payload": {
                    "query": textwrap.dedent(
                        """
                        subscription MyOperationName($userId: UserId) {
                            on_chat_message_sent(userId: $userId) { event }
                        }
                        """
                    ),
                    "variables": {"userId": userId},
                    "operationName": "MyOperationName",
                },
            }
        )
        return sub_id, comm
コード例 #4
0
async def test_main_usecase():
    """Test main use-case with the GraphQL over WebSocket."""

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(
        application=my_app, path="graphql/", subprotocols=["graphql-ws"]
    )

    print("Establish WebSocket connection and check a subprotocol.")
    connected, subprotocol = await comm.connect(timeout=TIMEOUT)
    assert connected, "Could not connect to the GraphQL subscriptions WebSocket!"
    assert subprotocol == "graphql-ws", "Wrong subprotocol received!"

    print("Initialize GraphQL connection.")
    await comm.send_json_to({"type": "connection_init", "payload": ""})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["type"] == "connection_ack"

    print("Make simple GraphQL query and check the response.")
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                query MyOperationName {
                    value
                }
            """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    assert "errors" not in resp["payload"]
    assert resp["payload"]["data"]["value"] == MyQuery.VALUE
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print("Subscribe to GraphQL subscription.")
    subscription_id = str(uuid.uuid4().hex)

    await comm.send_json_to(
        {
            "id": subscription_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    subscription MyOperationName {
                        on_chat_message_sent(userId: ALICE) {
                            event
                        }
                    }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )

    print("Trigger the subscription by mutation to receive notification.")
    uniq_id = str(uuid.uuid4().hex)
    message = "Hi!"
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    mutation MyOperationName($message: String!) {
                        send_chat_message(message: $message) {
                            message
                        }
                    }
                    """
                ),
                "variables": {"message": message},
                "operationName": "MyOperationName",
            },
        }
    )

    # Mutation response.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    assert "errors" not in resp["payload"]
    assert resp["payload"]["data"] == {"send_chat_message": {"message": message}}
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    # Subscription notification.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == subscription_id, "Notification id != subscription id!"
    assert resp["type"] == "data", "Type `data` expected!"
    assert "errors" not in resp["payload"]
    event = resp["payload"]["data"]["on_chat_message_sent"]["event"]
    assert json.loads(event) == {
        "userId": UserId.ALICE,
        "payload": message,
    }, "Subscription notification contains wrong data!"

    print("Disconnect and wait the application to finish gracefully.")
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #5
0
async def test_subscribe_unsubscribe():
    """Test subscribe-unsubscribe behavior with the GraphQL over WebSocket.

    0. Subscribe to GraphQL subscription: messages for Alice.
    1. Send STOP message and unsubscribe.
    2. Subscribe to GraphQL subscription: messages for Tom.
    3. Call unsubscribe method of the Subscription instance
    (via `kick_out_user` mutation).
    4. Execute some mutation.
    5. Check subscription notifications: there are no notifications.
    """

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(
        application=my_app, path="graphql/", subprotocols=["graphql-ws"]
    )

    print("Establish and initialize WebSocket GraphQL connection.")
    await comm.connect(timeout=TIMEOUT)
    await comm.send_json_to({"type": "connection_init", "payload": ""})
    await comm.receive_json_from(timeout=TIMEOUT)

    print("Subscribe to GraphQL subscription.")
    sub_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": sub_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    subscription MyOperationName {
                        on_chat_message_sent(userId: ALICE) { event }
                    }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )

    print("Stop subscription by id.")
    await comm.send_json_to({"id": sub_id, "type": "stop"})
    # Subscription notification with unsubscribe information.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == sub_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print("Subscribe to GraphQL subscription.")
    sub_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": sub_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    subscription MyOperationName {
                        on_chat_message_sent(userId: TOM) { event }
                    }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )

    print("Stop all subscriptions for TOM.")
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    mutation MyOperationName {
                        kick_out_user(userId: TOM) { success }
                    }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )
    # Mutation response.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"
    # Subscription notification with unsubscribe information.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == sub_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print("Trigger the subscription by mutation to receive notification.")
    uniq_id = str(uuid.uuid4().hex)
    message = "Is anybody here?"
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    mutation MyOperationName($message: String!) {
                        send_chat_message(message: $message) {
                            message
                        }
                    }
                    """
                ),
                "variables": {"message": message},
                "operationName": "MyOperationName",
            },
        }
    )
    # Mutation response.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    # Check notifications: there are no notifications! Previously,
    # we have unsubscribed from all subscriptions.
    assert await comm.receive_nothing() is True, "No notifications expected!"

    print("Disconnect and wait the application to finish gracefully.")
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #6
0
async def test_error_cases():
    """Test that server responds correctly when errors happen.

    Check that server responds with message of type `data` when there
    is a syntax error in the request or the exception in a resolver
    was raised. Check that server responds with message of type `error`
    when there was an exceptional situation, for example, field `query`
    of `payload` is missing or field `type` has a wrong value.
    """

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(
        application=my_app, path="graphql/", subprotocols=["graphql-ws"]
    )

    print("Establish & initialize the connection.")
    await comm.connect(timeout=TIMEOUT)
    await comm.send_json_to({"type": "connection_init", "payload": ""})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["type"] == "connection_ack"

    print("Check that query syntax error leads to the `error` response.")
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "wrong_type__(ツ)_/¯",
            "payload": {"variables": {}, "operationName": "MyOperationName"},
        }
    )
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "error", "Type `error` expected!"
    assert len(resp["payload"]) == 1, "Single error expected!"
    assert isinstance(
        resp["payload"]["errors"][0], str
    ), "Error must be of string type!"

    print(
        "Check that query syntax error leads to the `data` response "
        "with `errors` array."
    )
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    This list produces a syntax error!
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    payload = resp["payload"]
    assert payload["data"] is None
    errors = payload["errors"]
    assert len(errors) == 1, "Single error expected!"
    assert (
        "message" in errors[0] and "locations" in errors[0]
    ), "Response missing mandatory fields!"
    assert errors[0]["locations"] == [{"line": 1, "column": 1}]
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print(
        "Check that query syntax error leads to the `data` response "
        "with `errors` array."
    )
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    query MyOperationName {
                        value(issue_error: true)
                    }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    payload = resp["payload"]
    assert payload["data"]["value"] is None
    errors = payload["errors"]
    assert len(errors) == 1, "Single error expected!"
    assert errors[0]["message"] == MyQuery.VALUE
    assert "locations" in errors[0]
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print("Check multiple errors in the `data` message.")
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to(
        {
            "id": uniq_id,
            "type": "start",
            "payload": {
                "query": textwrap.dedent(
                    """
                    query {
                        projects {
                            path
                            wrong_filed
                        }
                    }

                    query a {
                        projects
                    }

                    { wrong_name }
                    """
                ),
                "variables": {},
                "operationName": "MyOperationName",
            },
        }
    )
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "data", "Type `data` expected!"
    payload = resp["payload"]
    assert payload["data"] is None
    errors = payload["errors"]
    assert len(errors) == 5, "Five errors expected!"
    # Message is here: `This anonymous operation must be
    # the only defined operation`.
    assert errors[0]["message"] == errors[3]["message"]
    assert "locations" in errors[2], "The `locations` field expected"
    assert "locations" in errors[4], "The `locations` field expected"
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp["id"] == uniq_id, "Response id != request id!"
    assert resp["type"] == "complete", "Type `complete` expected!"

    print("Disconnect and wait the application to finish gracefully.")
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #7
0
async def test_main_usecase():
    """Test main use-case with the GraphQL over WebSocket."""

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(application=my_app,
                                            path='graphql/',
                                            subprotocols='graphql-ws')

    print('Establish WebSocket connection and check a subprotocol.')
    connected, subprotocol = await comm.connect(timeout=TIMEOUT)
    assert connected, ('Could not connect to the GraphQL subscriptions '
                       'WebSocket!')
    assert subprotocol == 'graphql-ws', 'Wrong subprotocol received!'

    print('Initialize GraphQL connection.')
    await comm.send_json_to({'type': 'connection_init', 'payload': ''})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['type'] == 'connection_ack'

    print('Make simple GraphQL query and check the response.')
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to({
        'id': uniq_id,
        'type': 'start',
        'payload': {
            'query':
            textwrap.dedent('''
                query MyOperationName {
                    value
                }
            '''),
            'variables': {},
            'operationName':
            'MyOperationName',
        }
    })
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'data', 'Type `data` expected!'
    assert 'errors' not in resp['payload']
    assert resp['payload']['data']['value'] == MyQuery.VALUE
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'complete', 'Type `complete` expected!'

    print('Subscribe to GraphQL subscription.')
    uniq_id = str(uuid.uuid4().hex)
    sub_id = uniq_id
    sub_param1 = str(uuid.uuid4().hex)
    sub_param2 = str(uuid.uuid4().hex)
    await comm.send_json_to({
        'id': uniq_id,
        'type': 'start',
        'payload': {
            'query':
            textwrap.dedent('''
                subscription MyOperationName($sub_param1: String,
                                                $sub_param2: String) {
                    my_sub(param1: $sub_param1, param2: $sub_param2) {
                        event
                    }
                }
            '''),
            'variables': {
                'sub_param1': sub_param1,
                'sub_param2': sub_param2
            },
            'operationName':
            'MyOperationName',
        }
    })
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'data', 'Type `data` expected!'
    assert 'errors' not in resp['payload']
    assert resp['payload']['data'] == {}

    print('Trigger the subscription by mutation to receive notification.')
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to({
        'id': uniq_id,
        'type': 'start',
        'payload': {
            'query':
            textwrap.dedent('''
                mutation MyOperationName($mut_param: String) {
                    my_mutation(param: $mut_param, cmd: BROADCAST) {
                        ok
                    }
                }
            '''),
            'variables': {
                'mut_param': sub_param1
            },
            'operationName':
            'MyOperationName',
        }
    })

    # Mutation response.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'data', 'Type `data` expected!'
    assert 'errors' not in resp['payload']
    assert resp['payload']['data'] == {'my_mutation': {'ok': True}}
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'complete', 'Type `complete` expected!'

    # Subscription notification.
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == sub_id, ('Notification id != subscription id!')
    assert resp['type'] == 'data', 'Type `data` expected!'
    assert 'errors' not in resp['payload']
    assert json.loads(resp['payload']['data']['my_sub']['event']) == {
        'value': MySubscription.VALUE,
        'sub_param1': sub_param1,
        'sub_param2': sub_param2,
        'payload': MyMutation.PAYLOAD,
    }, 'Subscription notification contains wrong data!'

    print('Disconnect and wait the application to finish gracefully.')
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)
コード例 #8
0
async def test_error_cases():
    """Test that server responds correctly when errors happen.

    Check that server responds with message of type `error` when there
    is a syntax error in the request, but `data` with `errors` field
    responds to the exception in a resolver.
    """

    # Channels communicator to test WebSocket consumers.
    comm = ch_testing.WebsocketCommunicator(application=my_app,
                                            path='graphql/',
                                            subprotocols='graphql-ws')

    print('Establish & initialize the connection.')
    await comm.connect(timeout=TIMEOUT)
    await comm.send_json_to({'type': 'connection_init', 'payload': ''})
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['type'] == 'connection_ack'

    print('Check that query syntax error leads to the `error` response.')
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to({
        'id': uniq_id,
        'type': 'start',
        'payload': {
            'query':
            textwrap.dedent('''
                This list produces a syntax error!
            '''),
            'variables': {},
            'operationName':
            'MyOperationName',
        }
    })
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'error', 'Type `error` expected!'
    payload = resp['payload']
    assert ('message' in payload
            and 'locations' in payload), 'Response missing mandatory fields!'
    assert len(payload) == 2, 'Extra fields detected in the response!'

    print('Check that resolver error leads to the `data` message.')
    uniq_id = str(uuid.uuid4().hex)
    await comm.send_json_to({
        'id': uniq_id,
        'type': 'start',
        'payload': {
            'query':
            textwrap.dedent('''
                query MyOperationName {
                    value(issue_error: true)
                }
            '''),
            'variables': {},
            'operationName':
            'MyOperationName',
        }
    })
    resp = await comm.receive_json_from(timeout=TIMEOUT)
    assert resp['id'] == uniq_id, 'Response id != request id!'
    assert resp['type'] == 'data', 'Type `data` expected!'
    payload = resp['payload']
    assert payload['data']['value'] is None
    errors = payload['errors']
    assert len(errors) == 1, 'Single error expected!'
    assert errors[0]['message'] == MyQuery.VALUE
    assert 'locations' in errors[0]

    print('Disconnect and wait the application to finish gracefully.')
    await comm.disconnect(timeout=TIMEOUT)
    await comm.wait(timeout=TIMEOUT)