.. warning::

    This component is an *alpha*. Interfaces may change outside of the
    normal semver policy.

Getting Started with testing
============================

Dependencies
------------

This toolchain requires the ``responses`` library.

``globus_sdk.testing`` is tested to operate with the latest version of
``responses``.

Recommended Fixtures
--------------------

Under pytest, this is the recommended fixture for setting up responses and
guaranteeing that requests are sent to the production hostnames:

.. code-block:: python

    @pytest.fixture(autouse=True)
    def mocked_responses(monkeypatch):
        responses.start()
        monkeypatch.setitem(os.environ, "GLOBUS_SDK_ENVIRONMENT", "production")
        yield
        responses.stop()
        responses.reset()

Usage
-----

Activating Individual Responses
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Once ``responses`` has been activated, each response fixture can be loaded and
activated by name:

.. code-block:: python

    from globus_sdk.testing import load_response

    # load_response will add the response to `responses` and return it
    load_response("auth.get_identities")
    # "case" is used to have a single name map to multiple responses
    data = load_response("auth.get_identities", case="multiple")

Responses can also be activated by passing an SDK client method, bound or
unbound, as in:

.. code-block:: python

    import globus_sdk
    from globus_sdk.testing import load_response

    load_response(globus_sdk.AuthClient.get_identities)
    load_response(globus_sdk.AuthClient.get_identities, case="unauthorized")

    # or, with a bound method
    ac = globus_sdk.AuthClient()
    load_response(ac.get_identities, case="multiple")

Activating "Scenarios"
~~~~~~~~~~~~~~~~~~~~~~

Some sets of fixtures may describe a scenario, and therefore it's desirable to
load all of them at once:

.. code-block:: python

    from globus_sdk.testing import load_response_set

    fixtures = load_response_set("scenario.foo")

Getting Responses and ResponseSets without Activating
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you want to fetch a ``ResponseSet`` or ``RegisteredResponse`` without
activating it, you can do this via the ``get_response_set`` method. Responses
must always be part of a response set, and the default name for an individual
response is ``"default"``.

.. code-block:: python

    from globus_sdk import AuthClient
    from globus_sdk.testing import get_response_set

    # rset will not be activated
    rset = get_response_set(AuthClient.get_identities)
    # you can get an individual response from rset
    get_ids = rset.get("default")
    # you can manually activate a whole set
    rset.activate_all()
    # or just one response from it by name
    rset.activate("default")

Note that activating a whole response set may or may not make sense. For
example, the response set for ``AuthClient.get_identities`` provides various
responses for the same API call.

Registering Response Sets
~~~~~~~~~~~~~~~~~~~~~~~~~

You can register your own response sets dynamically, and then load them up with
the same ``load_response_set`` method. Note that custom response sets will
override the builtin response sets, if names match.

.. code-block:: python

    from globus_sdk.testing import load_response_set, register_response_set
    import uuid

    # register a scenario under which Globus Auth get_identities and Globus
    # Transfer operation_ls both return payloads of `{"foo": "bar"}`
    # use an autogenerated endpoint ID and put it into the response metadata
    # register_response_set takes dict data and converts it to fixtures
    endpoint_id = str(uuid.uuid1())
    register_response_set(
        "foobar",
        {
            "get_identities": {
                "service": "auth",
                "path": "/v2/api/identities",
                "json": {"foo": "bar"},
            },
            "operation_ls": {
                "service": "transfer",
                "path": f"/operation/endpoint/{endpoint_id}/ls",
                "json": {"foo": "bar"},
            },
        },
        metadata={
            "endpoint_id": endpoint_id,
        },
    )

    # activate the result, and get it as a ResponseSet
    fixtures = load_response_set("foobar")
    # you can then pull the epid from the metadata
    epid = fixtures.metadata["endpoint_id"]
    transfer_client.operation_ls(epid)

``register_response_set`` can therefore be used to load fixture data early in
a tetstsuite run (e.g. as an autouse session-level fixture), for reference
later in the testsuite.

Loading Responses without Registering
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Because ``RegisteredResponse`` takes care of resolving ``"auth"`` to the Auth
URL, ``"transfer"`` to the Transfer URL, and so forth, you might want to use
``globus_sdk.testing`` in lieu of ``responses`` even when registering single
responses for individual tests.

To support this mode of usage, ``load_response`` can take a
``RegisteredResponse`` instance, and ``load_response_set`` can take a
``ResponseSet`` instance.

Consider the following example of a parametrized test which uses
``load_response(RegisteredResponse(...))`` as a replacement for
``responses.add``:

.. code-block:: python

    from globus_sdk.testing import load_response, RegisteredResponse
    import pytest


    @pytest.mark.parametrize("message", ["foo", "bar"])
    def test_get_identities_sends_back_strange_message(message):
        load_response(
            RegisteredResponse(
                service="auth",
                path="/v2/api/identities",
                json={"message": message},
            )
        )

        ac = globus_sdk.AuthClient()
        res = ac.get_identities(usernames="foo@example.com")
        assert res["message"] == message


In this mode of usage, the response set registry is skipped altogether. It is
not necessary to name or organize the response fixtures in a way that is usable
outside of the specific test.

Using non-default responses.RequestsMock objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

By default, all methods in ``globus_sdk.testing`` which converse with
``responses`` use the default mock. This is the behavior offered by
``responses.add(...)`` and similar methods.

However, you can pass a custom ``RequestsMock`` if so desired to the following
methods:

* ``get_last_request``
* ``load_response_set``
* ``load_response``

as a keyword argument, ``requests_mock``.
e.g.


.. code-block:: python

    from globus_sdk.testing import get_last_request
    import responses

    custom_mock = responses.RequestsMock(...)
    ...

    get_last_request(requests_mock=custom_mock)
