def test_multiple_matches(self): with patch("sys.stdout", new=StringIO()) as output: data = { "id": "test", "request": { "url": "", "method": "get", }, "response": { "text": "Hello World", }, } mapping = MappingItem(data, "dummy.yml", os.getcwd()) Log.multiple_matches([ mapping, mapping, ]) self.assertIn( "- dummy.yml\nID: test\nScenario: default\nURL: \nMethod: GET\nQuery String: \n- dummy.yml\nID: " "test\nScenario: default\nURL: \nMethod: GET\nQuery String:", output.getvalue().strip(), )
def __init__(self): self.handler = MappingItemsManager() Log.ok("Server started successfully at {0}:{1}".format( "http://" + socket.gethostbyname(socket.gethostname()), cherrypy.config["server.socket_port"], ))
def mapping_item_for_mapping_request(self, request): # try find by scenario mappings = [ item for item in self.mappings if item.mock_scenario == Config.scenario and item.handles_mapping_request(request) ] if mappings: if Config.verbose: Log.info("Mapping found by scenario: {0}".format( Config.scenario)) return mappings # try find by default scenario mappings = [ item for item in self.mappings if item.mock_scenario == Constants.DEFAULT_SCENARIO and item.handles_mapping_request(request) ] if mappings: if Config.verbose: Log.info("Mapping found by default scenario") return mappings return []
def start(): Log.info("Initializing server...") # update config cherrypy.config.update({ "server.socket_port": Config.port, "server.socket_host": Config.host, "environment": "embedded", "tools.encode.text_only": False, "cors.expose.on": Config.cors, }) # update config for verbose mode if Config.verbose: cherrypy.config.update({ "log.screen": True, }) # cors if Config.cors: cherrypy_cors.install() Log.info("CORS enabled") # listen for signal def signal_handler(signal, frame): Log.info("Shutting down server...") cherrypy.engine.exit() Log.ok("Server shutdown successfully") signal.signal(signal.SIGINT, signal_handler) # start server cherrypy.quickstart(CherryPyServer())
def setup(self): is_image = self.body.body_type == BodyResponse.IMAGE is_file = self.body.body_type == BodyResponse.FILE is_json = self.body.body_type == BodyResponse.JSON if is_image or is_file: full_path = self.body_file_path() if os.path.isfile(full_path): self.headers["Accept-Ranges"] = "bytes" self.headers["Pragma"] = "public" self.headers["Content-Length"] = os.path.getsize(full_path) mimetype = mimetypes.guess_type(full_path) if mimetype: self.headers["Content-Type"] = mimetype[0] if is_file: self.headers[ "Content-Disposition"] = 'attachment; filename="{0}"'.format( os.path.basename(full_path)) else: Log.error("File not found: {0}".format(full_path), False) self.clear() self.status = 404 elif is_json: self.headers["Content-Type"] = "application/json"
def on_any_event(self, event): Log.info("Directory changed, rebuilding mapping settings...\n" "Path: %s" % event.src_path + "\nEvent type: %s" % event.event_type) Config.reload_sys_path_list() self.mapping_manager.parse_yaml_files() Log.ok("Mapping settings rebuilt successfully")
def test_fail_with_fatal(self): with patch("sys.stdout", new=StringIO()) as output: exited = False try: Log.fail("fail message", True) except SystemExit as e: if e.code == 10: exited = True self.assertIn("fail message", output.getvalue().strip()) self.assertEqual(exited, True)
def __init__(self, dic, base_path): self.base_path = base_path self.value = "" self._body_type = BodyResponse.NONE if dic: if "body_raw" in dic: self._body_type = BodyResponse.RAW self.file_name = "" self.value_from_raw(dic["body_raw"]) elif "body_file" in dic: self._body_type = BodyResponse.FILE self.file_name = dic["body_file"] self.value_from_file(self.file_name) elif "body_image" in dic: self._body_type = BodyResponse.IMAGE self.file_name = dic["body_image"] self.value_from_image(self.file_name) elif "body_json" in dic: self._body_type = BodyResponse.JSON self.file_name = "" self.value_from_object(dic["body_json"]) elif "body_python" in dic: self._body_type = BodyResponse.PYTHON self.file_name = dic["body_python"] # add sys path item to sys path list sys_path_list = dic["sys_path"] if "sys_path" in dic else [] if sys_path_list: for sys_path_item in sys_path_list: if sys_path_item == "auto": # auto = dir of python file full_path = File.real_path(self.base_path, self.file_name) full_path = os.path.dirname(full_path) else: # create path from item specified full_path = File.real_path(self.base_path, sys_path_item) if full_path not in sys.path: Log.info("Path added to sys.path: {0}".format( full_path)) sys.path.append(full_path)
def form_fields_matches(self, form_fields): has_form_fields = self.form_fields != {} if has_form_fields: for key in self.form_fields.keys(): if isinstance(form_fields, dict): if key not in form_fields: return False try: if not re.match(self.form_fields[key], form_fields[key]): return False except TypeError: Log.failed("Invalid regex: {0}".format( self.form_fields[key])) return False else: return False return True
def test_response(self): with patch("sys.stdout", new=StringIO()) as output: data = { "url": "http://localhost/pymocky", "method": "post", "body": "Hello World", } response = MappingResponse("test_id", "test_scenario", data, os.getcwd()) Log.log_response(response) self.assertIn("'mock_id': 'test_id'", output.getvalue().strip()) self.assertIn("'mock_scenario': 'test_scenario'", output.getvalue().strip()) self.assertIn("'method': 'post'", output.getvalue().strip()) self.assertIn("'body': 'Hello World'", output.getvalue().strip()) self.assertIn("'status': 200", output.getvalue().strip()) self.assertIn("'headers': {}", output.getvalue().strip()) self.assertIn("'headers': {}", output.getvalue().strip())
def test_request(self): with patch("sys.stdout", new=StringIO()) as output: data = { "url": "http://localhost/pymocky", "method": "post", "body": "Hello World", } request = MappingRequest("test_id", "test_scenario", data) Log.log_request(request, "http://localhost/pymocky") self.assertIn("'mock_id': 'test_id'", output.getvalue().strip()) self.assertIn("'mock_scenario': 'test_scenario'", output.getvalue().strip()) self.assertIn("'url': 'http://localhost/pymocky'", output.getvalue().strip()) self.assertIn("'method': 'post'", output.getvalue().strip()) self.assertIn("'headers': {}", output.getvalue().strip()) self.assertIn("'body': 'Hello World'", output.getvalue().strip()) self.assertIn("'form_fields': {}", output.getvalue().strip()) self.assertIn("'query_string': ''", output.getvalue().strip())
def parse_yaml_files(self): self.yaml_files = File.get_yaml_files(Config.path) self.mappings = [] for yaml_file in self.yaml_files: full_path = os.path.join(Config.path, yaml_file) with open(full_path, "r") as file: file_data = yaml.load(file, yaml.SafeLoader) if "mappings" in file_data: mappings = file_data["mappings"] if mappings and isinstance(mappings, list): for mapping in mappings: new_mapping = MappingItem( mapping, full_path, os.path.dirname(full_path), ) self.mappings.append(new_mapping) Log.info("Mappings loaded: {0:d}".format(len(self.mappings)))
def test_return_correct_body_for_multiple_response(self): item1 = Mock(file_name="file1") item1.request = "request1" item2 = Mock(file_name="file2") item2.request = "request2" # Config = Mock(verbose=True) mapper = CherryPyMapper() mapper.cherrypy = Mock() mapper.cherrypy.url = Mock(return_value="some url") body = Log.multiple_matches([item1, item2]) self.assertEqual( body, "- file1\nrequest1\n- file2\nrequest2\n", )
def process_python_data(self, process_data): full_path = File.real_path(self.base_path, self.body.file_name) # execute module "run" function try: if os.path.isfile(full_path): # return a dict from python file module_name = File.get_filename_without_extension(full_path) spec = importlib.util.spec_from_file_location( module_name, full_path, ) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) returned_data = module.run(process_data) # fill class with dict data self.status = (returned_data["status"] if "status" in returned_data else 500) self.headers = (returned_data["headers"] if "headers" in returned_data else {}) self.body.value = (returned_data["body"] if "body" in returned_data else "") else: Log.error("File not found: {0}".format(full_path), False) self.status = 404 except Exception as e: Log.error( "Error when execute file: {0}".format( os.path.basename(full_path)), False, ) Log.normal("Path: {0}".format(full_path)) Log.normal("Error: {0}".format(repr(e))) self.status = 500
def test_failed(self): with patch("sys.stdout", new=StringIO()) as output: Log.failed("failed message") self.assertIn("failed message", output.getvalue().strip())
def signal_handler(signal, frame): Log.info("Shutting down server...") cherrypy.engine.exit() Log.ok("Server shutdown successfully")
def __init__(self): self.parse_yaml_files() if Config.watch: Log.info("Live reload enabled") self.install_watchers()
def handle_request(self): request = self.cherrypy.to_mapper_request() if Config.verbose: Log.info("Request data:") Log.normal(request) else: Log.request_url(self.cherrypy.url()) items = self.mapping_handler.mapping_item_for_mapping_request(request) if len(items) == 0: self.cherrypy.response.status = 500 Log.failed("No response found for request: {0}".format( self.cherrypy.url())) return "No response found for request" if len(items) > 1: Log.warn("Matched {0:d} items, choosing the first one".format( len(items))) if Config.verbose: Log.multiple_matches(items) matched_item = items[0] response = matched_item.response if Config.verbose: Log.log_request(matched_item.request, self.cherrypy.url()) delay = Config.delay if delay > 0: delay = delay / 1000 if Config.verbose: Log.info("Delay: {0:.3f}ms".format(delay)) sleep(delay) if response.body.body_type == BodyResponse.PYTHON: response.process_python_data({"request": request}) self.cherrypy.response.status = response.status self.fill_headers(response.headers) if Config.verbose: Log.log_response(response) return response.body_response()
def __eq__(self, other): matches_method = self.method_matches(other.method) matches_url = self.url_matches(other.url) matches_body = self.body_matches(other.body) matches_headers = HeaderMatcher(self.headers).matches(other.headers) matches_form_fields = self.form_fields_matches(other.form_fields) matches_query_string = self.query_string_matches(other.query_string) if Config.verbose: Log.info("Mock tested:") Log.normal("Mock ID: {0}".format(self.mock_id)) Log.normal("Mock Scenario: {0}".format(self.mock_scenario)) Log.normal("Request URL: {0}".format(other.url)) Log.normal("Mock URL: {0}".format(self.url)) Log.normal("Matches Method: {0}".format( ("Yes" if matches_method else "No"))) Log.normal("Matches URL: {0}".format( ("Yes" if matches_url else "No"))) Log.normal("Matches Body: {0}".format( ("Yes" if matches_body else "No"))) Log.normal("Matches Headers: {0}".format( ("Yes" if matches_headers else "No"))) Log.normal("Matches Form Fields: {0}".format( ("Yes" if matches_form_fields else "No"))) Log.normal("Matches Query String: {0}".format( ("Yes" if matches_query_string else "No"))) return (matches_method and matches_url and matches_body and matches_headers and matches_form_fields and matches_query_string)
def test_separator(self): with patch("sys.stdout", new=StringIO()) as output: Log.separator() self.assertIn("-" * 80, output.getvalue().strip())
def test_colored(self): with patch("sys.stdout", new=StringIO()) as output: Log.colored("colored message", Fore.YELLOW) self.assertIn("colored message", output.getvalue().strip())
def test_normal(self): with patch("sys.stdout", new=StringIO()) as output: Log.normal("normal message") self.assertIn("normal message", output.getvalue().strip())
def test_url(self): with patch("sys.stdout", new=StringIO()) as output: Log.request_url("http://localhost/pymocky") self.assertIn("Request with url: http://localhost/pymocky", output.getvalue().strip())
def test_error(self): with patch("sys.stdout", new=StringIO()) as output: Log.error("error message", False) self.assertIn("error message", output.getvalue().strip())
def run(args): if args.update_scenario: scenario = args.update_scenario if not scenario: scenario = Constants.DEFAULT_SCENARIO Log.info("Changing to scenario {0}...".format(scenario)) try: r = requests.get( "{0}/pymocky/update-scenario?scenario={1}".format( args.server_host, scenario, )) if r.status_code == 200: Log.ok("Scenario updated") else: Log.failed("Scenario not updated: {0:d}".format( r.status_code)) except requests.exceptions.RequestException as e: Log.error("Scenario update error: {0}".format(e)) elif args.reload: Log.info("Reloading...") try: r = requests.get("{0}/pymocky/reload".format(args.server_host)) if r.status_code == 200: Log.ok("Reloaded") else: Log.failed("Reload failed: {0:d}".format(r.status_code)) except requests.exceptions.RequestException as e: Log.error("Reload error: {0}".format(e)) elif args.version: Log.normal("Version: {0}".format(__version__)) else: if not args.path: Log.error("Path argument is required (--path or -p)") Config.sys_path_list = sys.path.copy() CherryPyServer.start()
def test_info(self): with patch("sys.stdout", new=StringIO()) as output: Log.info("info message") self.assertIn("info message", output.getvalue().strip())