Пример #1
0
    def disconnect(self, callback):
        """
        Disconnect from the service.

        Note that even if this fails for some reason, the client will be in a disconnected state.

        :param callback: callback which is called when the disconnection is complete.

        :raises: :class:`azure.iot.device.iothub.pipeline.exceptions.PipelineNotRunning` if the
            pipeline has previously been shut down

        The following exceptions are not "raised", but rather returned via the "error" parameter
        when invoking "callback":

        :raises: :class:`azure.iot.device.iothub.pipeline.exceptions.ProtocolClientError`
        """
        self._verify_running()
        logger.debug("Starting DisconnectOperation on the pipeline")

        def on_complete(op, error):
            callback(error=error)

        self._pipeline.run_op(
            pipeline_ops_base.DisconnectOperation(callback=on_complete))
class TestMQTTTransportStageOnDisconnected(MQTTTransportStageTestConfigComplex
                                           ):
    @pytest.fixture(params=[False, True],
                    ids=["No error cause", "With error cause"])
    def cause(self, request, arbitrary_exception):
        if request.param:
            return arbitrary_exception
        else:
            return None

    @pytest.mark.it("Sends a DisconnectedEvent up the pipeline")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(
                pipeline_ops_base.ConnectOperation(callback=fake_callback),
                id="Pending ConnectOperation",
            ),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(
                    callback=fake_callback),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(
                pipeline_ops_base.DisconnectOperation(callback=fake_callback),
                id="Pending DisconnectOperation",
            ),
        ],
    )
    def test_disconnected_handler(self, stage, pending_connection_op, cause):
        stage._pending_connection_op = pending_connection_op
        assert stage.send_event_up.call_count == 0

        # Trigger disconnect
        stage.transport.on_mqtt_disconnected_handler(cause)

        assert stage.send_event_up.call_count == 1
        event = stage.send_event_up.call_args[0][0]
        assert isinstance(event, pipeline_events_base.DisconnectedEvent)

    @pytest.mark.it("Completes a pending DisconnectOperation successfully")
    def test_compltetes_pending_disconnect_op(self, mocker, stage, cause):
        # Create a pending DisconnectOperation
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger disconnect
        stage.transport.on_mqtt_disconnected_handler(cause)

        assert op.completed
        assert op.error is None

    @pytest.mark.it(
        "Swallows the exception that caused the disconnect, if there is a pending DisconnectOperation"
    )
    def test_completes_pending_disconnect_op_with_error(
            self, mocker, stage, arbitrary_exception):
        mock_swallow = mocker.patch.object(handle_exceptions,
                                           "swallow_unraised_exception")

        # Create a pending DisconnectOperation
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger disconnect with arbitrary cause
        stage.transport.on_mqtt_disconnected_handler(arbitrary_exception)

        # Exception swallower was called
        assert mock_swallow.call_count == 1
        assert mock_swallow.call_args == mocker.call(arbitrary_exception,
                                                     log_msg=mocker.ANY)

    @pytest.mark.it(
        "Completes (unsuccessfully) a pending operation that is NOT a DisconnectOperation, with the cause of the disconnection set as the error, if there is a cause provided"
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(
                pipeline_ops_base.ConnectOperation(callback=fake_callback),
                id="Pending ConnectOperation",
            ),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(
                    callback=fake_callback),
                id="Pending ReauthorizeConnectionOperation",
            ),
        ],
    )
    def test_comletes_with_cause_as_error_if_cause(self, mocker, stage,
                                                   pending_connection_op,
                                                   arbitrary_exception):
        stage._pending_connection_op = pending_connection_op
        assert not pending_connection_op.completed

        # Trigger disconnect with arbitrary cause
        stage.transport.on_mqtt_disconnected_handler(arbitrary_exception)

        assert pending_connection_op.completed
        assert pending_connection_op.error is arbitrary_exception

    @pytest.mark.it(
        "Completes (unsuccessfully) a pending operation that is NOT a DisconnectOperation with a ConnectionDroppedError if no cause is provided for the disconnection"
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(
                pipeline_ops_base.ConnectOperation(callback=fake_callback),
                id="Pending ConnectOperation",
            ),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(
                    callback=fake_callback),
                id="Pending ReauthorizeConnectionOperation",
            ),
        ],
    )
    def test_comletes_with_connection_dropped_error_as_error_if_no_cause(
            self, mocker, stage, pending_connection_op, arbitrary_exception):
        stage._pending_connection_op = pending_connection_op
        assert not pending_connection_op.completed

        # Trigger disconnect with no cause
        stage.transport.on_mqtt_disconnected_handler()

        assert pending_connection_op.completed
        assert isinstance(pending_connection_op.error,
                          transport_exceptions.ConnectionDroppedError)

    @pytest.mark.it(
        "Sends a ConnectionDroppedError to the background exception handler, if there is no pending operation when a disconnection occurs"
    )
    def test_no_pending_op(self, mocker, stage, cause):
        mock_handler = mocker.patch.object(handle_exceptions,
                                           "handle_background_exception")
        assert stage._pending_connection_op is None

        # Trigger disconnect
        stage.transport.on_mqtt_disconnected_handler(cause)

        assert mock_handler.call_count == 1
        exception = mock_handler.call_args[0][0]
        assert isinstance(exception,
                          transport_exceptions.ConnectionDroppedError)
        assert exception.__cause__ is cause

    @pytest.mark.it("Clears any pending operation on the stage")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(
                pipeline_ops_base.ConnectOperation(callback=fake_callback),
                id="Pending ConnectOperation",
            ),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(
                    callback=fake_callback),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(
                pipeline_ops_base.DisconnectOperation(callback=fake_callback),
                id="Pending DisconnectOperation",
            ),
        ],
    )
    def test_clears_pending(self, mocker, stage, pending_connection_op, cause):
        stage._pending_connection_op = pending_connection_op

        # Trigger disconnect
        stage.transport.on_mqtt_disconnected_handler(cause)

        assert stage._pending_connection_op is None
class TestMQTTTransportStageOnConnectionFailure(
        MQTTTransportStageTestConfigComplex):
    @pytest.mark.it("Does not send any events up the pipeline")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(1),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_does_not_send_event(self, mocker, stage, pending_connection_op,
                                 arbitrary_exception):
        stage._pending_connection_op = pending_connection_op

        # Trigger connection failure with an arbitrary cause
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)

        assert stage.send_event_up.call_count == 0

    @pytest.mark.it(
        "Completes a pending ConnectOperation unsuccessfully with the cause of connection failure as the error"
    )
    def test_fails_pending_connect_op(self, mocker, stage,
                                      arbitrary_exception):
        # Create a pending ConnectOperation
        op = pipeline_ops_base.ConnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connection failure with an arbitrary cause
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)

        assert op.completed
        assert op.error is arbitrary_exception
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending ReauthorizeConnectionOperation unsuccessfully with the cause of connection failure as the error"
    )
    def test_fails_pending_reconnect_op(self, mocker, stage,
                                        arbitrary_exception):
        # Create a pending ReauthorizeConnectionOperation
        op = pipeline_ops_base.ReauthorizeConnectionOperation(
            callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connection failure with an arbitrary cause
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)

        assert op.completed
        assert op.error is arbitrary_exception
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores a pending DisconnectOperation, and does not complete it")
    def test_ignores_pending_disconnect_op(self, mocker, stage,
                                           arbitrary_exception):
        # Create a pending DisconnectOperation
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connection failure with an arbitrary cause
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)

        # Assert nothing changed about the operation
        assert not op.completed
        assert stage._pending_connection_op is op

    @pytest.mark.it(
        "Triggers the background exception handler (with error cause) when the connection failure is unexpected"
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(
                pipeline_ops_base.DisconnectOperation(callback=fake_callback),
                id="Pending DisconnectOperation",
            ),
        ],
    )
    def test_unexpected_connection_failure(self, mocker, stage,
                                           arbitrary_exception,
                                           pending_connection_op):
        # A connection failure is unexpected if there is not a pending Connect/ReauthorizeConnection operation
        # i.e. "Why did we get a connection failure? We weren't even trying to connect!"
        mock_handler = mocker.patch.object(handle_exceptions,
                                           "handle_background_exception")
        stage._pending_connection_operation = pending_connection_op

        # Trigger connection failure with arbitrary cause
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)

        # Background exception handler has been called
        assert mock_handler.call_count == 1
        assert mock_handler.call_args == mocker.call(arbitrary_exception)
class TestMQTTTransportStageOnConnected(MQTTTransportStageTestConfigComplex):
    @pytest.mark.it("Sends a ConnectedEvent up the pipeline")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(1),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_sends_event_up(self, stage, pending_connection_op):
        stage._pending_connection_op = pending_connection_op
        # Trigger connect completion
        stage.transport.on_mqtt_connected_handler()

        assert stage.send_event_up.call_count == 1
        connect_event = stage.send_event_up.call_args[0][0]
        assert isinstance(connect_event, pipeline_events_base.ConnectedEvent)

    @pytest.mark.it("Completes a pending ConnectOperation successfully")
    def test_completes_pending_connect_op(self, mocker, stage):
        # Set a pending connect operation
        op = pipeline_ops_base.ConnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connect completion
        stage.transport.on_mqtt_connected_handler()

        # Connect operation completed successfully
        assert op.completed
        assert op.error is None
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending ReauthorizeConnectionOperation successfully")
    def test_completes_pending_reconnect_op(self, mocker, stage):
        # Set a pending reconnect operation
        op = pipeline_ops_base.ReauthorizeConnectionOperation(
            callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connect completion
        stage.transport.on_mqtt_connected_handler()

        # Reconnect operation completed successfully
        assert op.completed
        assert op.error is None
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores a pending DisconnectOperation when the transport connected event fires"
    )
    def test_ignores_pending_disconnect_op(self, mocker, stage):
        # Set a pending disconnect operation
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert not op.completed
        assert stage._pending_connection_op is op

        # Trigger connect completion
        stage.transport.on_mqtt_connected_handler()

        # Disconnect operation was NOT completed
        assert not op.completed
        assert stage._pending_connection_op is op
 def op(self, mocker):
     return pipeline_ops_base.DisconnectOperation(
         callback=mocker.MagicMock())
class TestMQTTTransportStageRunOpCalledWithDisconnectOperation(
        MQTTTransportStageTestConfigComplex, StageRunOpTestBase):
    @pytest.fixture
    def op(self, mocker):
        return pipeline_ops_base.DisconnectOperation(
            callback=mocker.MagicMock())

    @pytest.mark.it(
        "Sets the operation as the stage's pending connection operation")
    def test_sets_pending_operation(self, stage, op):
        stage.run_op(op)
        assert stage._pending_connection_op is op

    @pytest.mark.it("Cancels any already pending connection operation")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(
                pipeline_ops_base.ConnectOperation(callback=fake_callback),
                id="Pending ConnectOperation",
            ),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(
                    callback=fake_callback),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(
                pipeline_ops_base.DisconnectOperation(callback=fake_callback),
                id="Pending DisconnectOperation",
            ),
        ],
    )
    def test_pending_operation_cancelled(self, mocker, stage, op,
                                         pending_connection_op):
        # Set up a pending op
        stage._pending_connection_op = pending_connection_op
        assert not pending_connection_op.completed

        # Run the connect op
        stage.run_op(op)

        # Operation has been completed, with an OperationCancelled exception set indicating early cancellation
        assert pending_connection_op.completed
        assert type(pending_connection_op.error
                    ) is pipeline_exceptions.OperationCancelled

        # New operation is now the pending operation
        assert stage._pending_connection_op is op

    @pytest.mark.it("Performs an MQTT disconnect via the MQTTTransport")
    def test_mqtt_connect(self, mocker, stage, op):
        stage.run_op(op)
        assert stage.transport.disconnect.call_count == 1
        assert stage.transport.disconnect.call_args == mocker.call()

    @pytest.mark.it(
        "Completes the operation unsucessfully if there is a failure disconnecting via the MQTTTransport, using the error raised by the MQTTTransport"
    )
    def test_fails_operation(self, mocker, stage, op, arbitrary_exception):
        stage.transport.disconnect.side_effect = arbitrary_exception
        stage.run_op(op)
        assert op.completed
        assert op.error is arbitrary_exception

    @pytest.mark.it(
        "Resets the stage's pending connection operation to None, if there is a failure disconnecting via the MQTTTransport"
    )
    def test_clears_pending_op_on_failure(self, mocker, stage, op,
                                          arbitrary_exception):
        stage.transport.disconnect.side_effect = arbitrary_exception
        stage.run_op(op)
        assert stage._pending_connection_op is None
 def test_disconnect_while_disconnected(self, stage, mocker):
     op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
     stage.pipeline_root.connected = False
     stage.run_op(op)
     assert_callback_succeeded(op=op)
Пример #8
0
class TestMQTTProviderOnDisconnected(object):
    @pytest.mark.it(
        "Calls self.on_disconnected when the transport disconnected event fires"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(pipeline_ops_base.ReconnectOperation(1),
                         id="Pending ReconnectOperation"),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_disconnected_handler(self, stage, create_transport,
                                  pending_connection_op, cause):
        stage._pending_connection_op = pending_connection_op
        assert stage.previous.on_disconnected.call_count == 0
        stage.transport.on_mqtt_disconnected_handler(cause)
        assert stage.previous.on_disconnected.call_count == 1

    @pytest.mark.it(
        "Completes a pending DisconnectOperation with success when the transport disconnected event fires without an error cause"
    )
    def test_compltetes_pending_disconnect_op_when_no_error(
            self, mocker, stage, create_transport):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_disconnected_handler(None)
        assert_callback_succeeded(op=op)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending DisconnectOperation with success when the transport disconnected event fires with an error cause"
    )
    def test_completes_pending_disconnect_op_with_error(
            self, mocker, stage, create_transport, arbitrary_exception):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_disconnected_handler(arbitrary_exception)
        assert_callback_succeeded(op=op)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores an unrelated pending operation when the transport disconnected event fires"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(pipeline_ops_base.ReconnectOperation(1),
                         id="Pending ReconnectOperation"),
        ],
    )
    def test_ignores_unrelated_op(self, mocker, stage, create_transport,
                                  pending_connection_op, cause):
        stage._pending_connection_op = pending_connection_op
        stage.transport.on_mqtt_disconnected_handler(cause)
        # The unrelated pending operation is STILL the pending connection op
        assert stage._pending_connection_op is pending_connection_op

    @pytest.mark.it(
        "Triggers the unhandled exception handler (with ConnectionDroppedError) when the disconnect is unexpected"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(pipeline_ops_base.ReconnectOperation(1),
                         id="Pending ReconnectOperation"),
        ],
    )
    def test_unexpected_disconnect(self, mocker, stage, create_transport,
                                   pending_connection_op, cause):
        # A disconnect is unexpected when there is no pending operation, or a pending, non-Disconnect operation
        mock_handler = mocker.patch.object(handle_exceptions,
                                           "handle_background_exception")
        stage._pending_connection_op = pending_connection_op
        stage.transport.on_mqtt_disconnected_handler(cause)
        assert mock_handler.call_count == 1
        assert isinstance(mock_handler.call_args[0][0],
                          transport_exceptions.ConnectionDroppedError)
        assert mock_handler.call_args[0][0].__cause__ is cause
Пример #9
0
class TestMQTTProviderOnConnectionFailure(object):
    @pytest.mark.it(
        "Does not call self.on_connected when the transport connection failure event fires"
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(pipeline_ops_base.ReconnectOperation(1),
                         id="Pending ReconnectOperation"),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_does_not_call_connected_handler(self, stage, create_transport,
                                             arbitrary_exception,
                                             pending_connection_op):
        # This test is testing negative space - something the function does NOT do - rather than something it does
        stage._pending_connection_op = pending_connection_op
        assert stage.previous.on_connected.call_count == 0
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)
        assert stage.previous.on_connected.call_count == 0

    @pytest.mark.it(
        "Fails a pending ConnectOperation if the connection failure event fires"
    )
    def test_fails_pending_connect_op(self, mocker, stage, create_transport,
                                      arbitrary_exception):
        op = pipeline_ops_base.ConnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)
        assert_callback_failed(op=op, error=arbitrary_exception)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Fails a pending ReconnectOperation if the connection failure event fires"
    )
    def test_fails_pending_reconnect_op(self, mocker, stage, create_transport,
                                        arbitrary_exception):
        op = pipeline_ops_base.ReconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)
        assert_callback_failed(op=op, error=arbitrary_exception)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores a pending DisconnectOperation if the connection failure event fires"
    )
    def test_ignores_pending_disconnect_op(self, mocker, stage,
                                           create_transport,
                                           arbitrary_exception):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)
        # Assert nothing changed about the operation
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op

    @pytest.mark.it(
        "Triggers the unhandled exception handler (with error cause) when the connection failure is unexpected"
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_unexpected_connection_failure(self, mocker, stage,
                                           create_transport,
                                           arbitrary_exception,
                                           pending_connection_op):
        # A connection failure is unexpected if there is not a pending Connect/Reconnect operation
        # i.e. "Why did we get a connection failure? We weren't even trying to connect!"
        mock_handler = mocker.patch.object(handle_exceptions,
                                           "handle_background_exception")
        stage._pending_connection_operation = pending_connection_op
        stage.transport.on_mqtt_connection_failure_handler(arbitrary_exception)
        assert mock_handler.call_count == 1
        assert mock_handler.call_args[0][0] is arbitrary_exception
Пример #10
0
class TestMQTTProviderOnConnected(object):
    @pytest.mark.it(
        "Calls self.on_connected when the transport connected event fires")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(pipeline_ops_base.ReconnectOperation(1),
                         id="Pending ReconnectOperation"),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_connected_handler(self, stage, create_transport,
                               pending_connection_op):
        stage._pending_connection_op = pending_connection_op
        assert stage.previous.on_connected.call_count == 0
        stage.transport.on_mqtt_connected_handler()
        assert stage.previous.on_connected.call_count == 1

    @pytest.mark.it(
        "Completes a pending ConnectOperation with success when the transport connected event fires"
    )
    def test_completes_pending_connect_op(self, mocker, stage,
                                          create_transport):
        op = pipeline_ops_base.ConnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connected_handler()
        assert_callback_succeeded(op=op)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending ReconnectOperation with success when the transport connected event fires"
    )
    def test_completes_pending_reconnect_op(self, mocker, stage,
                                            create_transport):
        op = pipeline_ops_base.ReconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connected_handler()
        assert_callback_succeeded(op=op)
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores a pending DisconnectOperation when the transport connected event fires"
    )
    def test_ignores_pending_disconnect_op(self, mocker, stage,
                                           create_transport):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        stage.run_op(op)
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_connected_handler()
        # handler did NOT trigger a callback
        assert op.callback.call_count == 0
        assert stage._pending_connection_op is op
def op_disconnect(mocker):
    op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
    mocker.spy(op, "complete")
    return op
Пример #12
0
class TestMQTTTransportStageOnDisconnected(MQTTTransportStageTestBase):
    @pytest.mark.it(
        "Calls self.on_disconnected when the transport disconnected event fires"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(1),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_disconnected_handler(self, stage, create_transport,
                                  pending_connection_op, cause):
        stage._pending_connection_op = pending_connection_op
        assert stage.previous.on_disconnected.call_count == 0
        stage.transport.on_mqtt_disconnected_handler(cause)
        assert stage.previous.on_disconnected.call_count == 1

    @pytest.mark.it(
        "Completes a pending DisconnectOperation with success when the transport disconnected event fires without an error cause"
    )
    def test_compltetes_pending_disconnect_op_when_no_error(
            self, mocker, stage, create_transport):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        mocker.spy(op, "complete")
        stage.run_op(op)
        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_disconnected_handler(None)
        assert op.complete.call_count == 1
        assert op.complete.call_args == mocker.call()
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending DisconnectOperation with success when the transport disconnected event fires with an error cause"
    )
    def test_completes_pending_disconnect_op_with_error(
            self, mocker, stage, create_transport, arbitrary_exception):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        mocker.spy(op, "complete")
        stage.run_op(op)
        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op
        stage.transport.on_mqtt_disconnected_handler(arbitrary_exception)
        assert op.complete.call_count == 1
        assert op.complete.call_args == mocker.call()
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes an unrelated pending operation when the transport disconnected event fires"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(pipeline_ops_base.ConnectOperation(None),
                         id="Pending ConnectOperation"),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(None),
                id="Pending ReauthorizeConnectionOperation",
            ),
        ],
    )
    def test_completes_unrelated_op(self, mocker, stage, create_transport,
                                    pending_connection_op, cause):
        pending_connection_op.completed = False
        mocker.spy(pending_connection_op, "complete")
        stage._pending_connection_op = pending_connection_op
        stage.transport.on_mqtt_disconnected_handler(cause)
        assert stage._pending_connection_op is None
        assert pending_connection_op.complete.call_count == 1
        if cause:
            assert pending_connection_op.complete.call_args[1][
                "error"] == cause
        else:
            assert (type(pending_connection_op.complete.call_args[1]["error"])
                    == transport_exceptions.ConnectionDroppedError)

    @pytest.mark.it(
        "Triggers the unhandled exception handler (with ConnectionDroppedError) when the disconnect is unexpected"
    )
    @pytest.mark.parametrize(
        "cause",
        [
            pytest.param(None, id="No error cause"),
            pytest.param(SomeException(), id="With error cause"),
        ],
    )
    def test_unexpected_disconnect(self, mocker, stage, create_transport,
                                   cause):
        # A disconnect is unexpected when there is no pending operation, or a pending, non-Disconnect operation
        mock_handler = mocker.patch.object(handle_exceptions,
                                           "handle_background_exception")
        stage.transport.on_mqtt_disconnected_handler(cause)
        assert mock_handler.call_count == 1
        assert isinstance(mock_handler.call_args[0][0],
                          transport_exceptions.ConnectionDroppedError)
        assert mock_handler.call_args[0][0].__cause__ is cause
Пример #13
0
class TestMQTTTransportStageOnConnected(MQTTTransportStageTestBase):
    @pytest.mark.it(
        "Calls self.on_connected when the transport connected event fires")
    @pytest.mark.parametrize(
        "pending_connection_op",
        [
            pytest.param(None, id="No pending operation"),
            pytest.param(pipeline_ops_base.ConnectOperation(1),
                         id="Pending ConnectOperation"),
            pytest.param(
                pipeline_ops_base.ReauthorizeConnectionOperation(1),
                id="Pending ReauthorizeConnectionOperation",
            ),
            pytest.param(pipeline_ops_base.DisconnectOperation(1),
                         id="Pending DisconnectOperation"),
        ],
    )
    def test_connected_handler(self, stage, create_transport,
                               pending_connection_op):
        stage._pending_connection_op = pending_connection_op
        assert stage.previous.on_connected.call_count == 0
        stage.transport.on_mqtt_connected_handler()
        assert stage.previous.on_connected.call_count == 1

    @pytest.mark.it(
        "Completes a pending ConnectOperation with success when the transport connected event fires"
    )
    def test_completes_pending_connect_op(self, mocker, stage,
                                          create_transport):
        op = pipeline_ops_base.ConnectOperation(callback=mocker.MagicMock())
        mocker.spy(op, "complete")
        stage.run_op(op)

        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op

        stage.transport.on_mqtt_connected_handler()

        assert op.complete.call_count == 1
        assert op.complete.call_args == mocker.call()
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Completes a pending ReauthorizeConnectionOperation with success when the transport connected event fires"
    )
    def test_completes_pending_reauthorize_connection_op(
            self, mocker, stage, create_transport):
        op = pipeline_ops_base.ReauthorizeConnectionOperation(
            callback=mocker.MagicMock())
        mocker.spy(op, "complete")
        stage.run_op(op)

        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op

        stage.transport.on_mqtt_connected_handler()

        assert op.complete.call_count == 1
        assert op.complete.call_args == mocker.call()
        assert stage._pending_connection_op is None

    @pytest.mark.it(
        "Ignores a pending DisconnectOperation when the transport connected event fires"
    )
    def test_ignores_pending_disconnect_op(self, mocker, stage,
                                           create_transport):
        op = pipeline_ops_base.DisconnectOperation(callback=mocker.MagicMock())
        mocker.spy(op, "complete")
        stage.run_op(op)

        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op

        stage.transport.on_mqtt_connected_handler()

        # handler did NOT trigger a completion
        assert op.complete.call_count == 0
        assert stage._pending_connection_op is op