async def mock_scicrunch_service_api(fake_data_dir: Path, mock_env_devel_environment): assert mock_env_devel_environment["SCICRUNCH_API_KEY"] == os.environ.get( "SCICRUNCH_API_KEY") API_KEY = os.environ.get("SCICRUNCH_API_KEY") assert os.environ.get( "SCICRUNCH_API_BASE_URL") == "https://scicrunch.org/api/1" with aioresponses() as mock: # curl -X GET "https://scicrunch.org/api/1/resource/fields/autocomplete?field=Resource%20Name&value=octave" -H "accept: application/json mock.get( f"https://scicrunch.org/api/1/resource/fields/autocomplete?field=Resource%20Name&value=octave&key={API_KEY}", status=200, payload={ "data": [ { "rid": "SCR_000860", "original_id": "nlx_155680", "name": "cbiNifti: Matlab/Octave Nifti library", }, { "rid": "SCR_009637", "original_id": "nlx_155924", "name": "Pipeline System for Octave and Matlab", }, { "rid": "SCR_014398", "original_id": "SCR_014398", "name": "GNU Octave", }, ], "success": "true", }, ) # curl -X GET "https://scicrunch.org/api/1/resource/fields/view/SCR_018997" -H "accept: application/json" mock.get( f"https://scicrunch.org/api/1/resource/fields/view/SCR_018997?key={API_KEY}", status=200, payload=json.loads( (fake_data_dir / "get_osparc_resource_payload.json").read_text()), ) # curl -X GET "https://scicrunch.org/api/1/resource/versions/all/SCR_018997" -H "accept: application/json" mock.get( f"https://scicrunch.org/api/1/resource/versions/all/SCR_018997?key={API_KEY}", status=200, payload=json.loads( '{"data":[{"version":2,"status":"Curated","time":1598984801,"uid":34739,"username":"******","cid":null},{"version":1,"status":"Pending","time":1598898249,"uid":43,"username":"******","cid":30}],"success":true}' ), ) yield mock
async def test_unauntheticated_request_to_scicrunch(client): with aioresponses() as scicrunch_service_api_mock: scicrunch_service_api_mock.get( re.compile(r"^https://scicrunch\.org/api/1/resource/fields/view/.*"), status=web_exceptions.HTTPUnauthorized.status_code, ) with pytest.raises(web_exceptions.HTTPBadRequest): await client.post( "/v0/groups/sparc/classifiers/scicrunch-resources/SCR_018997", raise_for_status=True, )
async def _start_get_stop_services( client, push_services, user_id, project_id, api_version_prefix: str, save_state: Optional[bool], expected_save_state_call: bool, mocker, ): params = {} web_response = await client.post( f"/{api_version_prefix}/running_interactive_services", params=params) assert web_response.status == 400 params = { "user_id": "None", "project_id": "None", "service_uuid": "sdlfkj4", "service_key": "None", "service_tag": "None", # optional "service_basepath": "None", # optional } web_response = await client.post( f"/{api_version_prefix}/running_interactive_services", params=params) data = await web_response.json() assert web_response.status == 400, data params["service_key"] = "simcore/services/comp/somfunkyname-nhsd" params["service_tag"] = "1.2.3" web_response = await client.post( f"/{api_version_prefix}/running_interactive_services", params=params) data = await web_response.json() assert web_response.status == 404, data created_services = await push_services(0, 2) assert len(created_services) == 2 for created_service in created_services: service_description = created_service["service_description"] params["user_id"] = user_id params["project_id"] = project_id params["service_key"] = service_description["key"] params["service_tag"] = service_description["version"] service_port = created_service["internal_port"] service_entry_point = created_service["entry_point"] params["service_basepath"] = "/i/am/a/basepath" params["service_uuid"] = str(uuid.uuid4()) # start the service web_response = await client.post( f"/{api_version_prefix}/running_interactive_services", params=params) assert web_response.status == 201 assert web_response.content_type == "application/json" running_service_enveloped = await web_response.json() assert isinstance(running_service_enveloped["data"], dict) assert all(k in running_service_enveloped["data"] for k in [ "service_uuid", "service_key", "service_version", "published_port", "entry_point", "service_host", "service_port", "service_basepath", ]) assert (running_service_enveloped["data"]["service_uuid"] == params["service_uuid"]) assert running_service_enveloped["data"]["service_key"] == params[ "service_key"] assert (running_service_enveloped["data"]["service_version"] == params["service_tag"]) assert running_service_enveloped["data"][ "service_port"] == service_port service_published_port = running_service_enveloped["data"][ "published_port"] assert not service_published_port assert service_entry_point == running_service_enveloped["data"][ "entry_point"] service_host = running_service_enveloped["data"]["service_host"] assert service_host == f"test_{params['service_uuid']}" service_basepath = running_service_enveloped["data"][ "service_basepath"] assert service_basepath == params["service_basepath"] # get the service web_response = await client.request( "GET", f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}", ) assert web_response.status == 200 text = await web_response.text() assert web_response.content_type == "application/json", text running_service_enveloped = await web_response.json() assert isinstance(running_service_enveloped["data"], dict) assert all(k in running_service_enveloped["data"] for k in [ "service_uuid", "service_key", "service_version", "published_port", "entry_point", ]) assert (running_service_enveloped["data"]["service_uuid"] == params["service_uuid"]) assert running_service_enveloped["data"]["service_key"] == params[ "service_key"] assert (running_service_enveloped["data"]["service_version"] == params["service_tag"]) assert (running_service_enveloped["data"]["published_port"] == service_published_port) assert running_service_enveloped["data"][ "entry_point"] == service_entry_point assert running_service_enveloped["data"][ "service_host"] == service_host assert running_service_enveloped["data"][ "service_port"] == service_port assert running_service_enveloped["data"][ "service_basepath"] == service_basepath # stop the service query_params = {} if save_state: query_params.update( {"save_state": "true" if save_state else "false"}) mocked_save_state_cb = mocker.MagicMock( return_value=CallbackResult(status=200, payload={})) PASSTHROUGH_REQUESTS_PREFIXES = [ "http://127.0.0.1", "http://localhost", "unix://", # docker engine "ws://", # websockets ] with aioresponses(passthrough=PASSTHROUGH_REQUESTS_PREFIXES) as mock: # POST /http://service_host:service_port service_basepath/state ------------------------------------------------- mock.post( f"http://{service_host}:{service_port}{service_basepath}/state", status=200, callback=mocked_save_state_cb, ) web_response = await client.delete( f"/{api_version_prefix}/running_interactive_services/{params['service_uuid']}", params=query_params, ) if expected_save_state_call: mocked_save_state_cb.assert_called_once() text = await web_response.text() assert web_response.status == 204, text assert web_response.content_type == "application/json" data = await web_response.json() assert data is None
def mock_director_service( model_fake_factory, running_service_model_schema, registry_service_model_schema, user_id: int, project_id: str, project_nodes: List[Tuple[str, ...]], ): # helpers def fake_registry_service_model(**overrides): return model_fake_factory(registry_service_model_schema, **overrides) def fake_running_service_model(**overrides): return model_fake_factory(running_service_model_schema, **overrides) # fake director service "state" variables _fake_project_services = [ fake_registry_service_model(key=s[0], version=s[1]) for s in project_nodes ] _fake_running_services = [] def _get_running(): return [ s for s in _fake_running_services if s["service_state"] not in "complete failed".split() ] # # Mocks director's service API api/specs/director/openapi.yaml # with aioresponses() as mock: # GET /running_interactive_services ------------------------------------------------- url_pattern = ( r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services\?.*$" ) def get_running_interactive_services_handler(url: URL, **kwargs): assert int(url.query.get("user_id", 0)) in (user_id, 0) assert url.query.get("project_id") in (project_id, None) return CallbackResult(payload={"data": _get_running()}, status=200) mock.get( re.compile(url_pattern), callback=get_running_interactive_services_handler, repeat=True, ) # POST /running_interactive_services ------------------------------------------------- url_pattern = ( r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services\?.*$" ) def start_service_handler(url: URL, **kwargs): assert int(url.query["user_id"]) == user_id assert url.query["project_id"] == project_id service_uuid = url.query["service_uuid"] if any(s["service_uuid"] == service_uuid for s in _get_running()): return CallbackResult( payload={ "error": f"A service with the same uuid {service_uuid} already running exists" }, status=409, ) service_key = url.query["service_key"] service_tag = url.query.get("service_tag") try: service = next( s for s in _fake_project_services if s["key"] == service_key and s["version"] == service_tag) except StopIteration: return CallbackResult( payload={ "error": f"Service {service_key}:{service_tag} not found in project" }, status=404, ) starting_service = fake_running_service_model( service_key=service["key"], service_version=service["version"], service_basepath=url.query.get( "service_basepath", "" ), # predefined basepath for the backend service otherwise uses root service_state="starting", # let's assume it pulls very fast :-) ) _fake_running_services.append(starting_service) return CallbackResult(payload={"data": starting_service}, status=201) mock.post(re.compile(url_pattern), callback=start_service_handler, repeat=True) # DELETE /running_interactive_services/{service_uuid}------------------------------------------------- url_pattern = ( r"^http://[a-z\-_]*director:[0-9]+/v0/running_interactive_services/.+$" ) def stop_service_handler(url: URL, **kwargs): service_uuid = url.parts[-1] required_query_parameter = "save_state" if required_query_parameter not in kwargs["params"]: return CallbackResult( payload={ "error": f"stop_service endpoint was called without query parameter {required_query_parameter} {url}" }, status=500, ) try: service = next(s for s in _fake_running_services if s["service_uuid"] == service_uuid) service["service_state"] = "complete" except StopIteration: return CallbackResult(payload={"error": "service not found"}, status=404) return CallbackResult(status=204) mock.delete(re.compile(url_pattern), callback=stop_service_handler, repeat=True) # GET /services/{service_key}/{service_version}------------------------------------------------- url_pattern = r"^http://[a-z\-_]*director:[0-9]+/v0/services/.+/.+$" def get_service_by_key_version_handler(url: URL, **kwargs): service_key, service_version = url.parts[-2:] # TODO: validate against schema? # TODO: improve API: does it make sense to return multiple services services = [ s for s in _fake_project_services if s["key"] == service_key and s["version"] == service_version ] if services: return CallbackResult(payload={"data": services}, status=200) return CallbackResult( payload={ "error": f"Service {service_key}:{service_version} not found in project" }, status=404, ) mock.get( re.compile(url_pattern), callback=get_service_by_key_version_handler, repeat=True, ) # GET /service_extras/{service_key}/{service_version} # get_service_extras = re.compile( # r"^http://[a-z\-_]*director:[0-9]+/v0/service_extras/\w+/.+$" # ) ## mock.get(get_service_extras, status=200, payload={"data": {}}) yield mock