def get_color(self, hex, format='json', expect_status=200, verbose=True, **kwargs): """ Grab the information for one color as specified by the color's hexadecimal code. This endpoint returns a list of json dicts, even though there is only one color returned. The calling code will have to handle this list; for example: >>> res = self.color_endpoint.get_color('000000') >>> assert res.json()[0]['hex'] == '000000' :param hex: str, 6-character hex for color :param format: str, either `xml` or `json`; defaults to `json` :param verbose: Bool, whether to output additional logging information :param kwargs: dict of key-value pairs of additional parameters :return res: Requests response object """ kwargs['format'] = format url = self.endpoint_url + hex logger.info('hex: "%s"; url will be "%s".' % (hex, url)) logger.info('kwargs = %s' % kwargs) # pass to the base endpoints *requests* wrapper res = self.get(url, expect_status=expect_status, **kwargs) logger.info('Response status code is "%s:".' % res.status_code) if verbose: logger.info('Response json: \n%s' % plog(res.json())) return res
def get_colors(self, format='json', verbose=True, **kwargs): """ Grab the first n-results for the parameters passed to the colors endpoint. This endpoint returns a list of json dicts, even though there is only one color returned. The calling code will have to handle that. for example: >>> res = self.colors_endpoint.get_colors('000000') >>> assert res.json()[0]['hex'] == '000000' :param format: str, either `xml` or `json`; defaults to `json` :param verbose: Bool, whether to output additional logging information :param kwargs: dict of key-value pairs of additional parameters :return res: Request response object with a json list of dicts """ kwargs['format'] = format logger.info('kwargs = %s' % kwargs) # pass to the base endpoints *requests* wrapper res = self.get(self.endpoint_url, **kwargs) logger.info('Response status code is "%s:".' % res.status_code) if verbose: logger.info('Response json: \n%s' % plog(res.json())) return res
def test_get_single_name(self, genderizer, good_name): """ For the supplied name and expected gender, verify that the API response contains the name and expected gender. :param good_name: tuple, str name and str gender :return: None """ api = self.gender_endpoint name = good_name[0] expected_name = good_name[0] ex_gender = good_name[1] res = api.get_gender(name, verbose=True) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that we get back the same name that we requested assert res.json()['name'] == expected_name, \ 'expected "%s", but got "%s"' \ % (expected_name, res.json()['name']) # test point: verify that we got the expected gender assignment assert api.got_gender(res, ex_gender) # test point: verify that the json keys are correct assert api.verify_keys_in_response(res.json().keys()) # test point: verify that the json schema is correct # using the new schema validator approach assert api.validate_schema(res.json(), verbose=True)
def test_colordata(self, colourlovers, colors_data): """ This is a parametrized test case using a data source consisting of a list of lists. Pytest will iterate over the data model and call this test method once for each top level item in that data model. For each call, we get a list consisting of a color name, its hex code, and its RGB values. The hex string is the second element in this data, so we get that by grabbing it by calling `colors_data[1]`. Then, we need to drop the prepended `#` by calling `colors_data[1][1:]` :param colors_data: list consisting of str color name, str hex code, str RGB values :return: None """ logger.info('test data = "%s".' % colors_data) # setup: extract the hex string from the parametrized data input and make API request this_hex = colors_data[1][1:] res = self.color_endpoint.get_color(this_hex) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that we get back the same hex that we requested assert res.json()[0]['hex'] == this_hex # test point: verify that the json keys are correct assert self.color_endpoint.verify_keys_in_response(res.json()[0].keys()) # slow down the API calls so that we don't cause problems or exceed our welcome time.sleep(2)
def test_get_multiple_good_names(self, genderizer, multiple_good_names): """ For the supplied list of names and expected genders, verify that the API response contains the names and expected genders. :param multiple_good_names: tuple, list of str names and list of str genders :return: None """ api = self.gender_endpoint names = list(multiple_good_names[0]) res = api.get_gender(names, verbose=True) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # convert the param from list of names + list of genders # to list of name + gender pairs expected = list(zip(multiple_good_names[0], multiple_good_names[1])) logger.info('expected: %s' % expected) # convert the returned json into a list of name + gender pairs actual = [(n['name'], n['gender']) for n in res.json()] logger.info('actual: %s' % actual) # test point: verify that the json keys are correct for the first item assert api.verify_keys_in_response(res.json()[0].keys()) # test point: verify that the json schema is correct # using the new schema validator approach assert api.validate_schema(res.json()[0], verbose=True) # test point: verify a results item for each name passed to the API assert len(expected) == len(actual), \ 'FAIL: got %s results but expected %s.' \ % (len(actual), len(expected)) # loop over the results and validate each name/gender pair for i, item in enumerate(actual): # test point: verify that we get back the same name that we requested assert item[0] == expected[i][0], \ 'expected "%s", but got "%s"' % (expected[i][0], item[0]) # test point: verify the expected gender for the name assert item[1] == expected[i][1], \ 'expected "%s", but got "%s"' % (expected[i][1], item[1])
def test_exceed_multiple_request_limit(self, genderizer, more_than_11_names): """ Request 11 names in one API call; the limit is 10 so any name after the tenth gets ignored. :param more_than_11_names: tuple, list of str names and list of str genders :return: None """ api = self.gender_endpoint name = list(more_than_11_names[0]) # needs to be a list logger.info(f"\n----> name: {name}") res = api.get_gender(name, verbose=True) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # convert the param from list of names + list of genders # to list of name + gender pairs expected = list(zip(more_than_11_names[0], more_than_11_names[1]))[:10] # keep list to ten logger.info('expected: %s' % expected) # convert the returned json into a list of name + gender pairs actual = [(n['name'], n['gender']) for n in res.json()] logger.info('actual: %s' % actual) # test point: verify that the json keys are correct for the first item assert api.verify_keys_in_response(res.json()[0].keys()) # test point: verify a results item for each name passed to the API assert len(expected) == len(actual), \ 'FAIL: got %s results but expected %s.' \ % (len(actual), len(expected)) # loop over the results and validate each name/gender pair for i, item in enumerate(actual): # test point: verify that we get back the same name that we requested assert item[0] == expected[i][0], \ 'expected "%s", but got "%s"' % (expected[i][0], item[0]) # test point: verify the expected gender for the name assert item[1] == expected[i][1], \ 'expected "%s", but got "%s"' % (expected[i][1], item[1])
def write_cookies_to_file(cookies, url, fname=''): """ Save cookies as json to a file. :param cookies: list of dicts :param url: str, url for the current page :param fname: str, first part of filename, will be appended with timestamp; defaults to empty string :return: None """ filename = f"/{time.strftime('%H%M%S')}_{utils.path_proof_name(fname)}.txt" path = pytest.custom_namespace['testrun_cookies_output'] + filename with open(path, 'w') as f: f.write(f"{url}\n") # write the url as the first line f.write(utils.plog(cookies)) logger.info(f"\nSaved cookies: {path}.")
def test_get_colors(self, colourlovers): """ Simple test for the "colors" endpoint, using no data. This just looks at the structure of the returned json. :return: None """ # setup: make API request res = self.colors_endpoint.get_colors() # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that the json keys are correct assert self.color_endpoint.verify_keys_in_response(res.json()[0].keys())
def test_int_as_name(self, genderizer): """ Integers are converted to strings by the API. """ api = self.gender_endpoint name = 42 expected_name = '42' res = api.get_gender(name, verbose=True) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that we get back the same name that we requested actual_name = res.json()['name'] assert actual_name == expected_name, \ f"expected '{expected_name}', but got '{actual_name}'."
def write_sdk_response_to_file(response, sdk_app, fname=''): """ Save the response from an SDK. Unlike typical API calls, the SDK calls don't necessarily have the standard http request data patterns. These SDKs will be wrappers in the welkin/integrations folder. :param response: SDK response, probably json or a dict :param sdk_app: str name of the SDK :param fname: str, first part of filename, will be appended with timestamp; defaults to empty string :return: None """ filename = f"/{time.strftime('%H%M%S')}_{utils.path_proof_name(fname)}.json" path = pytest.custom_namespace['testrun_integrations_log_folder'] + filename with open(path, 'a') as f: f.write(utils.plog(response))
def write_console_log_to_file(log, url, fname=''): """ Write the chrome devtools console logs to a file. :param log: dict :param url: str, url for the current page :param fname: str, first part of filename, will be appended with timestamp; defaults to empty string :return: None """ filename = f"/{time.strftime('%H%M%S')}_{utils.path_proof_name(fname)}.json" path = pytest.custom_namespace['testrun_console_log_folder'] + filename # add key/value for the page url log.update({'_page': url}) with open(path, 'a') as f: f.write(utils.plog(log)) logger.info(f"\nSaved console logs (and bad headers): {path}.")
def test_get_color(self, colourlovers): """ Simple test for the "color" endpoint, using hardcoded data. :return: None """ # setup: make API request data = '000000' res = self.color_endpoint.get_color(data) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that we get back the same hex that we requested assert res.json()[0]['hex'] == data # test point: verify that the json keys are correct assert self.color_endpoint.verify_keys_in_response(res.json()[0].keys())
def write_network_log_to_file(log, url, fname=''): """ Save the browser network log as json to a file. :param log: :param url: str, url for the current page :param fname: str, first part of filename, will be appended with timestamp; defaults to empty string :return: None """ # note: json files don't allow comments, so we'd not be able # to write the url to the file filename = f"/{time.strftime('%H%M%S')}_{utils.path_proof_name(fname)}.json" path = pytest.custom_namespace['testrun_network_log_folder'] + filename wrapper = {} wrapper['_page'] = url wrapper['chrome network logs'] = log with open(path, 'a') as f: f.write(utils.plog(wrapper)) logger.info(f"\nSaved browser network log: {path}.")
def get_password_from_aws(self, aws_session, decrypt=False, verbose=False): """ Make an AWS call to retrieve the password for this user. :param aws_session: AWS session object :param decrypt: bool, True to decrypt the AWS param value :param verbose: bool, True to output additional information :return: None """ resource = 'ssm' # AWS System manager is used for parameters client = AWSClient(aws_session, resource_name=resource) # make the AWS get parameter call res = client.get_parameter_data(aws_key_name=self.password_key, decrypt=decrypt) if verbose: logging_data = utils.plog(utils.strip_password_for_reporting(res)) msg1 = f"AWS response:\n{logging_data}" msg2 = f"\n\n\n{'#-' * 50}\nNOTE: Don't log passwords!" \ f"\n{msg1}\n{'#-' * 50}\n\n\n" logger.info(msg2) self.password = res['Parameter']['Value']
def _write_local_to_file(data, event, pageobject_name, source_url, output_url): """ Write the local storage to a json file in the webstorage folder. Note: several sweord key-value pairs will be inserted into that data. :param data: dict of local storage log pulled from the browser :param event: str, descriptor for an interaction with the React app :param pageobject_name: str, name of pageobject :param source_url: str, full url of the page that generated the logs :param output_url: str, full local path for the output file :return: None """ # add key/value for the page url data.update({'_storage type': 'local'}) data.update({'_page': source_url}) data.update({'_page object name': pageobject_name}) data.update({'_precipitating event': event}) with open(output_url, 'a') as f: f.write(utils.plog(data)) logger.info(f"Saved local storage log: {output_url}.")
def test_gender_not_resolved(self, genderizer, bad_name): """ For the supplied problematic name, verify that the API response contains the name and null gender. :param bad_name: str name :return: None """ api = self.gender_endpoint name = bad_name ex_gender = None res = api.get_gender(name, verbose=True) # test point: verify the correct response for a correct api call assert res.status_code == 200 logger.info(utils.plog(res.json())) # test point: verify that we get back the same name that we requested assert res.json()['name'] == bad_name, \ 'expected "%s", but got "%s"' % (bad_name, res.json()['name']) # verify that we got gender assignment of None assert self.gender_endpoint.got_gender(res, ex_gender)
def write_request_to_file(response, url, fname=''): """ Save the request and response headers and payload. Note that the request info is extracted from the response content. :param response: requests Response object :param url: str, url for the current page :param fname: str, first part of filename, will be appended with timestamp; defaults to empty string :return: None """ filename = f"/{time.strftime('%H%M%S')}_{utils.path_proof_name(fname)}.txt" path = pytest.custom_namespace['testrun_requests_log_folder'] + filename boundary = None with open(path, 'a') as f: f.write(f"{url}\n\n") # write the url as the first line f.write("###### REQUEST ####### \n") f.write("HEADERS\n") try: # the request header MUST have the Content-Type key-value pair # if not, raise an exception and kill the test content_type = response.request.headers['Content-Type'] except KeyError: msg = f"Missing header content-type field in request to {url}" logger.error(msg) raise ValueError(msg) if 'boundary' in content_type: # this is a POST, it must have the multi-part content-type, so # extract the boundary str used between binary attachments boundary = content_type[content_type.index('boundary=') + 9:] f.write(utils.plog(response.request.headers)) if response.request.body: # it will be as BODY (None) in file because content_type is not found in headers f.write(f"\n\nBODY ({content_type})\n") if isinstance(response.request.body, bytes): # this was a binary upload, so decode it as a # unicode str in order to write it to the file logger.warning("response.request.body cast as string.") f.write("--decoded from bytes--\n") if boundary: # this is a multi-part form body # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition body = response.request.body.decode( 'utf-8', 'backslashreplace') cleaned_body = body.replace('\r\n\r\n\n\r\n', '\n\n') f.write(cleaned_body) else: f.write( "don't know what happened with this request body;\n" "probably a POST with bytes.") else: try: f.write(utils.plog(json.loads(response.request.body))) except UnicodeDecodeError: logger.warning("got UnicodeDecodeError.") f.write("-- byte string snipped --") except json.decoder.JSONDecodeError: logger.warning("got json.decoder.JSONDecodeError.") f.write(utils.plog(response.request.body)) f.write("\n\n###### RESPONSE ####### \n") f.write("HEADERS\n") f.write(utils.plog(response.headers)) f.write("\n\nRESPONSE STATUS CODE\n") f.write(f"response server status: {response.status_code}") f.write("\n\nPAYLOAD\n") try: f.write(utils.plog(response.json())) except json.decoder.JSONDecodeError: # this could be an xml byte response f.write(utils.plog(response.content)) f.write(f"\n\n{'~' * 45}\n\n") logger.info(f"Saved headers: {path}")