def test_retry_with_authorizer(client): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=401, body="Unauthorized")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # an authorizer class which does nothing but claims to support handling of # unauthorized errors dummy_authz_calls = [] class DummyAuthorizer(globus_sdk.authorizers.GlobusAuthorizer): def get_authorization_header(self): dummy_authz_calls.append("set_authz") return "foo" def handle_missing_authorization(self): dummy_authz_calls.append("handle_missing") return True authorizer = DummyAuthorizer() client.authorizer = authorizer # no sign of an error in the client res = client.get("/bar") assert res.http_status == 200 assert res["baz"] == 1 # ensure that setting authz was called twice (once for each request) # and that between the two calls, handle_missing_authorization was called once assert dummy_authz_calls == ["set_authz", "handle_missing", "set_authz"]
def test_no_retry_with_authorizer_no_handler(client): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=401, body="Unauthorized")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # an authorizer class which does nothing and does not claim to handle # unauthorized errors dummy_authz_calls = [] class DummyAuthorizer(globus_sdk.authorizers.GlobusAuthorizer): def get_authorization_header(self): dummy_authz_calls.append("set_authz") return "foo" def handle_missing_authorization(self): dummy_authz_calls.append("handle_missing") return False authorizer = DummyAuthorizer() client.authorizer = authorizer # error gets raised in client (no retry) with pytest.raises(globus_sdk.GlobusAPIError) as excinfo: client.get("/bar") assert excinfo.value.http_status == 401 # only two calls, single setting of authz and a call to ask about handling the error assert dummy_authz_calls == ["set_authz", "handle_missing"]
def test_allow_redirects_false(client): # based on a real response from 'GET https://auth.globus.org/' load_response( RegisteredResponse( path="https://foo.api.globus.org/bar", status=302, headers={ "Date": "Fri, 15 Apr 2022 15:35:44 GMT", "Content-Type": "text/html", "Content-Length": "138", "Connection": "keep-alive", "Server": "nginx", "Location": "https://www.globus.org/", }, body="""\ <html> <head><title>302 Found</title></head> <body> <center><h1>302 Found</h1></center> <hr><center>nginx</center> </body> </html> """, )) # NOTE: this test isn't very "real" because of where `responses` intercepts the # request/response action # even without `allow_redirects=False`, this test would pass # if we find a better way of testing redirect behavior, consider removing this test res = client.request("GET", "/bar", allow_redirects=False) assert res.http_status == 302
def test_stream_true(client): load_response( RegisteredResponse( path="https://foo.api.globus.org/bar", json={"foo": "bar"}, )) res = client.request("GET", "/bar", stream=True) assert res.http_status == 200 # forcing JSON evaluation still works as expected (this must force the download / # evaluation of content) assert res["foo"] == "bar"
def test_no_retry_401_no_authorizer(client): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=401, body="Unauthorized")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # error gets raised in client (no retry) with pytest.raises(globus_sdk.GlobusAPIError) as excinfo: client.get("/bar") assert excinfo.value.http_status == 401
def test_persistent_connection_error(client): for _i in range(6): load_response( RegisteredResponse( path="https://foo.api.globus.org/bar", body=requests.ConnectionError("foo-err"), )) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) with pytest.raises(globus_sdk.GlobusConnectionError): client.get("/bar")
def test_list_jobs(timer_client): meta = load_response(timer_client.list_jobs).metadata response = timer_client.list_jobs() assert response.http_status == 200 assert set( meta["job_ids"]) == {job["job_id"] for job in response.data["jobs"]}
def test_create_job(timer_client, start, interval): meta = load_response(timer_client.create_job).metadata transfer_client = TransferClient() transfer_client.get_submission_id = lambda *_0, **_1: {"value": "mock"} transfer_data = TransferData(transfer_client, GO_EP1_ID, GO_EP2_ID) timer_job = TimerJob.from_transfer_data(transfer_data, start, interval) response = timer_client.create_job(timer_job) assert response.http_status == 201 assert response.data["job_id"] == meta["job_id"] timer_job = TimerJob.from_transfer_data(dict(transfer_data), start, interval) response = timer_client.create_job(timer_job) assert response.http_status == 201 assert response.data["job_id"] == meta["job_id"] req_body = json.loads(get_last_request().body) if isinstance(start, datetime): assert req_body["start"] == start.isoformat() else: assert req_body["start"] == start if isinstance(interval, timedelta): assert req_body["interval"] == interval.total_seconds() else: assert req_body["interval"] == interval assert req_body["callback_url"] == slash_join(get_service_url("actions"), "/transfer/transfer/run")
def test_identity_map_batch_limit(client): meta1 = load_response(client.get_identities).metadata meta2 = load_response(client.get_identities, case="sirosen").metadata # setup the ID map with a size limit of 1 idmap = globus_sdk.IdentityMap(client, id_batch_size=1) idmap.add(meta2["id"]) idmap.add(meta1["id"]) # no requests yet... assert len(responses.calls) == 0 # do the first lookup, using the second ID to be added # only one call should be made assert idmap[meta1["id"]]["username"] == meta1["username"] assert len(responses.calls) == 1 # 1 ID left unresolved assert len(idmap.unresolved_ids) == 1 # the last (only) API call was by ID with one ID last_req = get_last_request() assert "usernames" not in last_req.params assert last_req.params == {"ids": meta1["id"]} # second lookup works as well assert idmap[meta2["id"]]["username"] == meta2["username"] assert len(responses.calls) == 2 # no IDs left unresolved assert len(idmap.unresolved_ids) == 0 # the last API call was by ID with one ID last_req = get_last_request() assert "usernames" not in last_req.params assert last_req.params == {"ids": meta2["id"]}
def test_retry_on_transient_error(client, mocksleep, error_status): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=error_status, body="Uh-oh!")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # no sign of an error in the client res = client.get("/bar") assert res.http_status == 200 assert res["baz"] == 1 # there was a sleep (retry was triggered) mocksleep.assert_called_once()
def test_retry_disabled_via_tune(client, mocksleep): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=500, body="Uh-oh!")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # the error is seen by the client (automatic retry does not hide it) with pytest.raises(globus_sdk.GlobusAPIError) as excinfo: with client.transport.tune(max_retries=0): client.get("/bar") assert excinfo.value.http_status == 500 # there was a no sleep (retry was not triggered) mocksleep.assert_not_called()
def test_search_role_list(search_client): meta = load_response(search_client.get_role_list).metadata res = search_client.get_role_list(meta["index_id"]) assert res.http_status == 200 role_list = res["role_list"] assert isinstance(role_list, list) assert len(role_list) == 2
def test_transport_retry_limit(client, mocksleep): # this limit is a safety to protect against a bad policy causing infinite retries client.transport.max_retries = 2 for _i in range(3): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=500, body="Uh-oh!")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) with pytest.raises(globus_sdk.GlobusAPIError): client.get("/bar") assert mocksleep.call_count == 2
def test_search_role_delete(search_client): meta = load_response(search_client.delete_role).metadata res = search_client.delete_role(meta["index_id"], meta["role_id"]) assert res.http_status == 200 assert res["success"] is True assert res["deleted"]["index_id"] == meta["index_id"] assert res["deleted"]["id"] == meta["role_id"]
def test_delete_storage_gateway(client): meta = load_response(client.delete_storage_gateway).metadata res = client.delete_storage_gateway(meta["id"]) assert res.http_status == 200 # confirm top level access to response data assert res["code"] == "success" assert res["message"] == "Operation successful"
def test_my_groups_simple(groups_client): meta = load_response(groups_client.get_my_groups).metadata res = groups_client.get_my_groups() assert res.http_status == 200 assert isinstance(res, ArrayResponse) assert isinstance(res.data, list) assert set(meta["group_names"]) == {g["name"] for g in res}
def test_retry_on_network_error(client, mocksleep): # set the response to be a requests NetworkError -- responses will raise the # exception when the call is made load_response( RegisteredResponse( path="https://foo.api.globus.org/bar", body=requests.ConnectionError("foo-err"), )) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) # no sign of an error in the client res = client.get("/bar") assert res.http_status == 200 assert res["baz"] == 1 # there was a sleep (retry was triggered) mocksleep.assert_called_once()
def test_update_storage_gateway(client): meta = load_response(client.update_storage_gateway).metadata # as in the create test, an empty update document is not very realistic # but because there's no request validation, this is fine res = client.update_storage_gateway(meta["id"], {}) assert res.http_status == 200 # confirm top level access to response data assert res["code"] == "success" assert res["message"] == "Operation successful"
def test_get_identities_success(usernames, client): data = load_response(client.get_identities) res = client.get_identities(usernames=usernames) assert [x["id"] for x in res] == [data.metadata["id"]] lastreq = get_last_request() assert lastreq.params == { "usernames": "*****@*****.**", "provision": "false", # provision defaults to false }
def test_create_storage_gateway(client): meta = load_response(client.create_storage_gateway).metadata # the SDK does not validate the create document, so an empty document is fine, if # unrealistic, with a mocked response res = client.create_storage_gateway({}) assert res.http_status == 200 # confirm top level access to storage gateway data assert res["id"] == meta["id"] assert res["display_name"] == meta["display_name"]
def test_identity_map_del(client): meta = load_response(client.get_identities).metadata idmap = globus_sdk.IdentityMap(client) identity_id = idmap[meta["username"]]["id"] del idmap[identity_id] assert idmap.get(meta["username"])["id"] == identity_id # we've only made one request so far assert len(responses.calls) == 1 # but a lookup by ID after a del is going to trigger another request because we've # invalidated the cached ID data and are asking the IDMap to look it up again assert idmap.get(identity_id)["username"] == meta["username"] assert len(responses.calls) == 2
def test_identity_map_prepopulated_cache(client): meta = load_response(client.get_identities).metadata # populate the cache, even with nulls it should stop any lookups from happening cache = {meta["id"]: None, meta["username"]: None} idmap = globus_sdk.IdentityMap(client, cache=cache) # no requests yet... assert len(responses.calls) == 0 # do the lookups assert idmap[meta["id"]] is None assert idmap[meta["username"]] is None # still no calls made assert len(responses.calls) == 0
def test_retry_limit(client, mocksleep, num_errors, expect_err): # N errors followed by a success for _i in range(num_errors): load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", status=500, body="Uh-oh!")) load_response( RegisteredResponse(path="https://foo.api.globus.org/bar", json={"baz": 1})) if expect_err: with pytest.raises(globus_sdk.GlobusAPIError): client.get("/bar") else: # no sign of an error in the client res = client.get("/bar") assert res.http_status == 200 assert res["baz"] == 1 # default num retries = 5 assert mocksleep.call_count == min(num_errors, 5)
def test_search_post_query_simple(search_client, query_doc): meta = load_response(search_client.post_search).metadata res = search_client.post_search(meta["index_id"], query_doc) assert res.http_status == 200 data = res.data assert isinstance(data, dict) assert data["gmeta"][0]["entries"][0]["content"]["foo"] == "bar" req = get_last_request() assert req.body is not None req_body = json.loads(req.body) assert req_body == dict(query_doc)
def test_get_group_include(groups_client, include_param): meta = load_response(groups_client.get_group).metadata expect_param = (",".join(include_param) if not isinstance(include_param, str) else include_param) res = groups_client.get_group(group_id=meta["group_id"], include=include_param) assert res.http_status == 200 assert "Claptrap" in res["name"] req = get_last_request() assert req.body is None parsed_qs = urllib.parse.parse_qs(urllib.parse.urlparse(req.url).query) assert len(parsed_qs["include"]) == 1 assert parsed_qs["include"][0] == expect_param
def test_search_role_create(search_client): meta = load_response(search_client.create_role).metadata send_data = { "role_name": meta["role_name"], "principal": "urn:globus:auth:identity:" + meta["identity_id"], } res = search_client.create_role(meta["index_id"], send_data) assert res.http_status == 200 assert res["index_id"] == meta["index_id"] assert res["role_name"] == "writer" last_req = get_last_request() sent = json.loads(last_req.body) assert sent == send_data
def test_search_query_simple(search_client): meta = load_response(search_client.search).metadata res = search_client.search(meta["index_id"], q="foo") assert res.http_status == 200 data = res.data assert isinstance(data, dict) assert data["gmeta"][0]["entries"][0]["content"]["foo"] == "bar" req = get_last_request() assert req.body is None parsed_qs = urllib.parse.parse_qs(urllib.parse.urlparse(req.url).query) assert parsed_qs == { "q": ["foo"], "advanced": ["False"], "limit": ["10"], "offset": ["0"], }
def test_get_storage_gateway(client, include_param): meta = load_response(client.get_storage_gateway).metadata res = client.get_storage_gateway(meta["id"], include=include_param) assert res.http_status == 200 # confirm top level access to storage gateway data assert res["id"] == meta["id"] assert res["display_name"] == meta["display_name"] req = get_last_request() assert req.body is None parsed_qs = urllib.parse.parse_qs(urllib.parse.urlparse(req.url).query) if include_param is None: assert parsed_qs == {} elif isinstance(include_param, str): assert parsed_qs == {"include": [include_param]} else: assert parsed_qs == {"include": [",".join(include_param)]}
def test_search_post_query_arg_overrides(search_client, query_doc): meta = load_response(search_client.post_search).metadata res = search_client.post_search(meta["index_id"], query_doc, limit=100, offset=150) assert res.http_status == 200 data = res.data assert isinstance(data, dict) assert data["gmeta"][0]["entries"][0]["content"]["foo"] == "bar" req = get_last_request() assert req.body is not None req_body = json.loads(req.body) assert req_body != dict(query_doc) assert req_body["q"] == query_doc["q"] assert req_body["limit"] == 100 assert req_body["offset"] == 150 # important! these should be unchanged (no side-effects) assert query_doc["limit"] == 10 assert query_doc["offset"] == 0
def test_get_storage_gateway_list(client, include_param): meta = load_response(client.get_storage_gateway_list).metadata expect_ids = meta["ids"] res = client.get_storage_gateway_list(include=include_param) assert res.http_status == 200 # confirm iterable and sanity check some fields assert len(list(res)) > 0 for sg in res: assert sg["DATA_TYPE"] == "storage_gateway#1.0.0" assert "id" in sg assert "display_name" in sg assert [sg["id"] for sg in res] == expect_ids req = get_last_request() assert req.body is None parsed_qs = urllib.parse.parse_qs(urllib.parse.urlparse(req.url).query) if include_param is None: assert parsed_qs == {} elif isinstance(include_param, str): assert parsed_qs == {"include": [include_param]} else: assert parsed_qs == {"include": [",".join(include_param)]}