def test_create_repository_error(requests_mock: Mocker, git_repo_manager: GitRepoManagerClient): repo_name = 'fake_repo' get_repo_mock = requests_mock.get( f'{git_repo_manager.base_url}/api/v1/repos/{FAKE_USER}/{repo_name}', status_code=404) post_repo_mock = requests_mock.post( f'{git_repo_manager.base_url}/api/v1/admin/users/{FAKE_USER}/repos', status_code=422) with pytest.raises(HTTPError): git_repo_manager.create_repository(username=FAKE_USER, repository_name=repo_name) assert get_repo_mock.call_count == 1 assert post_repo_mock.call_count == 1
def test_query_permissions_use_cache( api_url: str, permission_query_path: str, opa_provider: mlrun.api.utils.auth.providers.opa.Provider, requests_mock: requests_mock_package.Mocker, ): auth_info = mlrun.api.schemas.AuthInfo(user_id="user-id") project_name = "project-name" opa_provider.add_allowed_project_for_owner(project_name, auth_info) mocker = requests_mock.post(f"{api_url}{permission_query_path}", json={}) assert (opa_provider.query_permissions( f"/projects/{project_name}/resource", mlrun.api.schemas.AuthorizationAction.create, auth_info, ) is True) assert mocker.call_count == 0
def test_upload_failed(self, *, rmock: requests_mock.Mocker) -> None: rmock.post("http://test/api/files/test", text='["test/testpkg.deb"]', status_code=500) with self.assertRaises(AptlyAPIException): self.fapi.upload("test", os.path.join(os.path.dirname(__file__), "testpkg.deb"))
def test_create_project_failures( api_url: str, iguazio_client: mlrun.api.utils.clients.iguazio.Client, requests_mock: requests_mock_package.Mocker, ): """ The exception handling is generic so no need to test it for every action (read/update/delete). There are basically 2 options: 1. Validations failure - in this case job won't be triggered, and we'll get an error (http) response from iguazio 2. Processing failure - this will happen inside the job, so we'll see the job finishing with failed state, sometimes the job result will have nice error messages for us """ session = "1234" project = _generate_project() # mock validation failure error_message = "project name invalid or something" requests_mock.post( f"{api_url}/api/projects", status_code=http.HTTPStatus.BAD_REQUEST.value, json={ "errors": [ {"status": http.HTTPStatus.BAD_REQUEST.value, "detail": error_message} ] }, ) with pytest.raises( mlrun.errors.MLRunBadRequestError, match=rf"(.*){error_message}(.*)" ): iguazio_client.create_project( session, project, ) # mock job failure - with nice error message in result job_id = "1d4c9d25-9c5c-4a34-b052-c1d3665fec5e" requests_mock.post( f"{api_url}/api/projects", json=functools.partial( _verify_creation, iguazio_client, project, session, job_id ), ) error_message = "failed creating project in Nuclio for example" job_result = json.dumps( {"status": http.HTTPStatus.BAD_REQUEST.value, "message": error_message} ) _mock_job_progress( api_url, requests_mock, session, job_id, mlrun.api.utils.clients.iguazio.JobStates.failed, job_result, ) with pytest.raises( mlrun.errors.MLRunBadRequestError, match=rf"(.*){error_message}(.*)" ): iguazio_client.create_project( session, project, ) # mock job failure - without nice error message (shouldn't happen, but let's test) _mock_job_progress( api_url, requests_mock, session, job_id, mlrun.api.utils.clients.iguazio.JobStates.failed, ) with pytest.raises(mlrun.errors.MLRunRuntimeError): iguazio_client.create_project( session, project, )
def test_request_additional_headers(api, requests_mock: RequestsMocker): custom_headers = {'custom': 'value'} requests_mock.post('', request_headers={**api.default_headers, **custom_headers}) api.post('', headers={'custom': 'value'})
def test_compute_nps_report( self, mock_requests: requests_mock.Mocker, unused_mock_sentry_init: mock.MagicMock) -> None: """Test computing the NPS report on multiple user feedback.""" self._db.user.insert_many([ { '_id': mongomock.ObjectId('123400000012340000001234'), 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 0, 'generalFeedbackComment': 'The app was blocked for me :-(', }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 9, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 6, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { '_id': mongomock.ObjectId('000056780000005678000000'), 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 9, 'generalFeedbackComment': 'You rock!', }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, # User registered and answered the NPS after the to_date. { 'registeredAt': '2017-11-11T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-11T16:00:00Z', 'score': 0, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-11T13:00:00Z', }], }, # User registered but did not answer the NPS. { 'registeredAt': '2017-11-01T12:00:00Z', 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, ]) mock_requests.post('https://slack/') feedback_report.main([ 'nps', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run' ], io.StringIO()) slack_json = mock_requests.request_history[0].json() self.assertEqual( '4 users answered the NPS survey (out of 5 - 80% answer rate) ' 'for a global NPS of *25.0%*\n' '*9*: 2 users\n' '*6*: 1 user\n' '*0*: 1 user\n' 'And here are the individual comments:\n' '[Score: 9] ObjectId("000056780000005678000000")\n' '> You rock!\n' '[Score: 0] ObjectId("123400000012340000001234")\n' '> The app was blocked for me :-(', slack_json['attachments'][0]['text'])
def test_compute_agreement_report( self, mock_requests: requests_mock.Mocker, unused_mock_sentry_init: mock.MagicMock) -> None: """Test computing the agreement report on multiple user feedback.""" self._db.user.insert_many([ { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'challengeAgreementScore': 2, }, }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'challengeAgreementScore': 5, }, }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'challengeAgreementScore': 2, }, }], }, # User did not answer the feedback. { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, }], }, # User registered and answered the feeback after the to_date. { 'registeredAt': '2017-11-11T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-11T13:00:00Z', 'diagnostic': {}, 'feedback': { 'challengeAgreementScore': 2, }, }], }, ]) mock_requests.post('https://slack/') feedback_report.main([ 'agreement', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run' ], io.StringIO()) slack_json = mock_requests.request_history[0].json() self.assertEqual( '3 project challenges were evaluated in the app (out of 4 - 75% answer rate) ' 'for a global average agreement of *2.0/4*\n' '4/4: 1 project\n' '1/4: 2 projects', slack_json['attachments'][0]['text']) self.assertIn(':ok_hand: Agreement Report', slack_json['attachments'][0]['title'])
def test_compute_nps_report_no_comments( self, mock_requests: requests_mock.Mocker, unused_mock_sentry_init: mock.MagicMock) -> None: """Test computing the NPS report on multiple user feedback.""" self._db.user.insert_many([ { '_id': mongomock.ObjectId('123400000012340000001234'), 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 0, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 9, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 6, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, { '_id': mongomock.ObjectId('000056780000005678000000'), 'registeredAt': '2017-11-01T12:00:00Z', 'netPromoterScoreSurveyResponse': { 'respondedAt': '2017-11-01T16:00:00Z', 'score': 9, }, 'emailsSent': [{ 'campaignId': 'nps', 'sentAt': '2017-11-01T13:00:00Z', }], }, ]) mock_requests.post('https://slack/') feedback_report.main([ 'nps', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run' ], io.StringIO()) slack_json = mock_requests.request_history[0].json() self.assertEqual( '4 users answered the NPS survey (out of 4 - 100% answer rate) ' 'for a global NPS of *25.0%*\n' '*9*: 2 users\n' '*6*: 1 user\n' '*0*: 1 user\n' 'There are no individual comments.', slack_json['attachments'][0]['text'])
def test_sends_correct_service_id(self, m: Mocker): m.post('/job', text=RESPONSE_JOB_RUNNING, status_code=201) piazza.execute('test-service-id', {}) self.assertEqual('test-service-id', m.request_history[0].json()['data']['serviceId'])
def test_calls_correct_url(self, m: Mocker): m.post('/job', text=RESPONSE_JOB_RUNNING, status_code=201) piazza.execute('test-service-id', {}) self.assertEqual('https://test-piazza-host.localdomain/job', m.request_history[0].url)
def test_throws_when_credentials_are_rejected(self, m: Mocker): m.post('/deployment', text=RESPONSE_ERROR_GENERIC, status_code=401) with self.assertRaises(piazza.Unauthorized): piazza.deploy(data_id='test-data-id', poll_interval=0, max_poll_attempts=2)
def test_handles_http_errors_gracefully_during_creation(self, m: Mocker): m.post('/deployment', text=RESPONSE_ERROR_GENERIC, status_code=500) with self.assertRaises(piazza.ServerError): piazza.deploy(data_id='test-data-id', poll_interval=0, max_poll_attempts=2)
def test_new_user( self, mock_requests: requests_mock.Mocker, mock_get_job_proto: mock.MagicMock, mock_get_city_proto: mock.MagicMock) -> None: """Auth request with PE Connect for a new user.""" def _match_correct_code(request: 'requests_mock._RequestObjectProxy') -> bool: return 'code=correct-code' in (request.text or '') mock_requests.post( 'https://authentification-candidat.pole-emploi.fr/connexion/oauth2/access_token?' 'realm=/individu', json={ 'access_token': '123456', 'nonce': 'correct-nonce', 'scope': 'api_peconnect-individuv1 openid profile email api_peconnect-coordonneesv1 ' 'coordonnees competences', 'token_type': 'Bearer', }, additional_matcher=_match_correct_code) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-competences/v1/competences', headers={'Authorization': 'Bearer 123456'}, json=[ { 'codeAppellation': '86420', 'codeRome': 'A1234', }, { 'codeAppellation': '86421', 'codeRome': 'A1235', }, ], ) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-individu/v1/userinfo', headers={'Authorization': 'Bearer 123456'}, json={ 'email': '*****@*****.**', 'family_name': 'CORPET', 'gender': 'male', 'given_name': 'PASCAL', 'sub': 'pe-connect-user-id-1', }) mock_requests.get( 'https://api.emploi-store.fr/partenaire/peconnect-coordonnees/v1/coordonnees', headers={'Authorization': 'Bearer 123456'}, json={ 'codeINSEE': '69386', 'address1': '55 rue du lac', }) mock_get_city_proto.return_value = geo_pb2.FrenchCity(name='Lyon', city_id='69123') mock_get_job_proto.return_value = job_pb2.Job(name='Plombier') response = self.app.post( '/api/user/authenticate', data='{"peConnectCode": "correct-code", "peConnectNonce": "correct-nonce"}', content_type='application/json') auth_response = self.json_from_response(response) self.assertTrue(auth_response['isNewUser']) user = auth_response['authenticatedUser'] self.assertEqual('pe-connect-user-id-1', user.get('peConnectId')) self.assertTrue(user.get('hasAccount')) self.assertEqual('Pascal', user.get('profile', {}).get('name')) self.assertEqual('Corpet', user.get('profile', {}).get('lastName')) self.assertEqual('MASCULINE', user.get('profile', {}).get('gender')) self.assertEqual([True], [p.get('isIncomplete') for p in user.get('projects', [])]) self.assertEqual('Lyon', user['projects'][0].get('city', {}).get('name')) self.assertEqual('69123', user['projects'][0].get('city', {}).get('cityId')) self.assertEqual('Plombier', user['projects'][0].get('targetJob', {}).get('name')) user_id = user['userId'] self.assertEqual([user_id], [str(u['_id']) for u in self._user_db.user.find()]) mock_get_city_proto.assert_called_once_with('69123') mock_get_job_proto.assert_called_once() self.assertEqual(('86420', 'A1234'), mock_get_job_proto.call_args[0][1:])
def test_throws_on_http_error_when_setting_default_style( self, m: requests_mock.Mocker): m.post('/geoserver/rest/styles') m.put('/geoserver/rest/layers/all_detections', status_code=500) with self.assertRaises(geoserver.InstallError): geoserver.install_style('test-style-id')
def test_connection_error(self, mock: requests_mock.Mocker) -> None: mock.post("https://api.broadcasthe.net/", exc=requests.ConnectionError()) with self.assertRaises(requests.ConnectionError): self.call()
def test_throws_when_data_is_missing(self, m: Mocker): mangled_response = json.loads(RESPONSE_TRIGGER_LIST) mangled_response.pop('data') m.post('/trigger/query', json=mangled_response) with self.assertRaisesRegex(piazza.InvalidResponse, 'missing `data`'): piazza.get_triggers('test-name')
def test_throws_when_credentials_are_rejected(self, m: Mocker): m.post('/trigger/query', text=RESPONSE_TRIGGER_LIST, status_code=401) with self.assertRaises(piazza.Unauthorized): piazza.get_triggers('test-name')
def test_sends_correct_input_parameters(self, m: Mocker): m.post('/job', text=RESPONSE_JOB_RUNNING, status_code=201) piazza.execute('test-service-id', {'foo': 'bar'}) self.assertEqual({'foo': 'bar'}, m.request_history[0].json()['data']['dataInputs'])
def test_compute_stars_report( self, mock_requests: requests_mock.Mocker, unused_mock_sentry_init: mock.MagicMock) -> None: """Test computing the stars report on multiple user feedback.""" self._db.user.insert_many([ { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'score': 2, 'text': 'Well well', }, }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'score': 5, }, }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, 'feedback': { 'score': 2, }, }], }, # User did not answer the feedback. { 'registeredAt': '2017-11-01T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-01T13:00:00Z', 'diagnostic': {}, }], }, # User registered and answered the NPS after the to_date. { 'registeredAt': '2017-11-11T12:00:00Z', 'projects': [{ 'createdAt': '2017-11-11T13:00:00Z', 'diagnostic': {}, 'feedback': { 'score': 2, }, }], }, ]) mock_requests.post('https://slack/') feedback_report.main([ 'stars', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run' ], io.StringIO()) slack_json = mock_requests.request_history[0].json() self.assertEqual( '3 projects were scored in the app (out of 4 - 75% answer rate) ' 'for a global average of *3.0 :star:*\n' ':star::star::star::star::star:: 1 project\n' ':star::star:: 2 projects\n' 'And here is the individual comment:\n' '[:star::star:]\n' '> Well well', slack_json['attachments'][0]['text'])
def test_handles_http_errors_gracefully(self, m: Mocker): m.post('/job', text=RESPONSE_ERROR_GENERIC, status_code=500) with self.assertRaises(piazza.ServerError): piazza.execute('test-service-id', {})
def test_compute_rer_report( self, mock_requests: requests_mock.Mocker, unused_mock_sentry_init: mock.MagicMock) -> None: """Test computing the RER report on multiple user feedback.""" self._db.user.insert_many([ { 'registeredAt': '2017-11-01T12:00:00Z', 'employmentStatus': [{ 'createdAt': '2017-11-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'employmentStatus': [{ 'createdAt': '2017-11-01T13:00:00Z', 'seeking': 'STILL_SEEKING', 'bobHasHelped': 'NOT_AT_ALL', }], }, { 'registeredAt': '2017-11-01T12:00:00Z', 'employmentStatus': [{ 'createdAt': '2017-11-01T13:00:00Z', 'seeking': 'STILL_SEEKING', 'bobHasHelped': 'YES_SOMEHOW', }], }, { 'registeredAt': '2017-06-01T12:00:00Z', 'employmentStatus': [ # Only the 3rd status is within range. { 'createdAt': '2017-09-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }, { 'createdAt': '2017-10-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }, { 'createdAt': '2017-11-01T13:00:00Z', 'seeking': 'STOP_SEEKING', }, { 'createdAt': '2017-12-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }, ], }, # User answered before and then after the date. { 'registeredAt': '2017-06-01T12:00:00Z', 'employmentStatus': [ { 'createdAt': '2017-09-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }, { 'createdAt': '2017-12-01T13:00:00Z', 'seeking': 'STILL_SEEKING', }, ], }, # User answered the RER after the to_date. { 'registeredAt': '2017-11-01T12:00:00Z', 'employmentStatus': [{ 'createdAt': '2017-11-09T13:00:00Z', 'seeking': 'STILL_SEEKING', }], }, ]) mock_requests.post('https://slack/') feedback_report.main([ 'rer', '--from', '2017-10-30', '--to', '2017-11-07', '--no-dry-run' ], io.StringIO()) slack_json = mock_requests.request_history[0].json() self.assertEqual( '4 users have answered the survey, *25.0%* have stopped seeking:\n' '*STILL_SEEKING*: 3 users (50.0% said Bob helped - excluding N/A)\n' '*STOP_SEEKING*: 1 user (0.0% said Bob helped - excluding N/A)', slack_json['attachments'][0]['text'])
def test_throws_when_credentials_are_rejected(self, m: Mocker): m.post('/job', text=RESPONSE_ERROR_GENERIC, status_code=401) with self.assertRaises(piazza.Unauthorized): piazza.execute('test-service-id', {})
def test_request_data_param_passed_as_json(api, requests_mock: RequestsMocker): post_mock = requests_mock.post('') api.post('', data={}) assert post_mock.last_request._request.body.decode() == '{}'
def test_throws_when_job_id_is_missing(self, m: Mocker): truncated_response = json.loads(RESPONSE_JOB_CREATED) truncated_response['data'].pop('jobId') m.post('/job', json=truncated_response, status_code=201) with self.assertRaises(piazza.InvalidResponse): piazza.execute('test-service-id', {})
def test_request_timeout_limits(timeout, exc_ctx, api, requests_mock: RequestsMocker): requests_mock.post('') with exc_ctx: api.post('', timeout=timeout)
def test_calls_correct_url(self, m: Mocker): m.post('/trigger/query', text=RESPONSE_TRIGGER_LIST) piazza.get_triggers('test-name') self.assertEqual('https://test-piazza-host.localdomain/trigger/query', m.request_history[0].url)
def test_upload_file(self, *, rmock: requests_mock.Mocker) -> None: rmock.post("http://test/api/files/test", text='["test/testpkg.deb"]') self.assertSequenceEqual( self.fapi.upload("test", os.path.join(os.path.dirname(__file__), "testpkg.deb")), ['test/testpkg.deb'], )
def test_returns_a_list_of_triggers(self, m: Mocker): m.post('/trigger/query', text=RESPONSE_TRIGGER_LIST) triggers = piazza.get_triggers('test-name') self.assertIsInstance(triggers, list) self.assertEqual(['test-trigger-id-1', 'test-trigger-id-2'], list(map(lambda d: d['triggerId'], triggers)))
def test_get_cube_info(self, m: requests_mock.Mocker): m.post(f'{self.ENDPOINT_URL}oauth/token', json={ "access_token": "4ccsstkn983456jkfde", "token_type": "bearer" }) m.post(f'{self.ENDPOINT_URL}cubegens/info', json={ "status": "ok", "result": { "dataset_descriptor": { "data_id": "CHL", "data_type": "dataset", "crs": "WGS84", "bbox": [12.2, 52.1, 13.9, 54.8], "time_range": ["2018-01-01", "2010-01-06"], "time_period": "4D", "data_vars": { "B01": { "name": "B01", "dtype": "float32", "dims": ["time", "lat", "lon"], }, "B02": { "name": "B02", "dtype": "float32", "dims": [ "time", "lat", "lon" ], }, "B03": { "name": "B03", "dtype": "float32", "dims": ["time", "lat", "lon"], } }, "spatial_res": 0.05, "dims": {"time": 0, "lat": 54, "lon": 34} }, "size_estimation": { "image_size": [34, 54], "tile_size": [34, 54], "num_variables": 3, "num_tiles": [1, 1], "num_requests": 0, "num_bytes": 0 }, "cost_estimation": { 'required': 3782, 'available': 234979, 'limit': 10000 } } }) result = self.generator.get_cube_info(self.REQUEST) self.assertIsInstance(result, CubeInfoWithCostsResult) cube_info = result.result self.assertIsInstance(cube_info, CubeInfoWithCosts) self.assertIsInstance(cube_info.dataset_descriptor, DatasetDescriptor) self.assertIsInstance(cube_info.size_estimation, dict) self.assertIsInstance(cube_info.cost_estimation, CostEstimation) self.assertEqual(3782, cube_info.cost_estimation.required) self.assertEqual(234979, cube_info.cost_estimation.available) self.assertEqual(10000, cube_info.cost_estimation.limit)
def test_gracefully_handles_errors(self, m: Mocker): m.post('/trigger/query', text=RESPONSE_ERROR_GENERIC, status_code=500) with self.assertRaises(piazza.ServerError): piazza.get_triggers('test-name')