def test_skips_empty_values( empty_config_mock, defaults_options, select_repobee_section ): """Test that empty values are not written to the configuration file.""" defaults_options = collections.OrderedDict( (option, c * 10) for option, c in zip( get_configurable_default_argnames(), string.ascii_lowercase, ) ) empty_option = list(defaults_options.keys())[3] defaults_options[empty_option] = "" with patch( "builtins.input", side_effect=list(defaults_options.values()) ), patch("pathlib.Path.exists", autospec=True, return_value=False): configwizard.callback(None, plug.Config(empty_config_mock)) del defaults_options[empty_option] confparser = configparser.ConfigParser() confparser.read(str(empty_config_mock)) assert empty_option not in confparser[plug.Config.CORE_SECTION_NAME] for key, value in defaults_options.items(): assert confparser[plug.Config.CORE_SECTION_NAME][key] == value
def test_creates_directory(defaults_options, select_repobee_section, tmp_path): config_file = tmp_path / "path" / "to" / "config.ini" with patch("builtins.input", side_effect=list(defaults_options.values())): configwizard.callback(None, plug.Config(config_file)) assert config_file.exists()
def test_config_parent_path_is_relative_to_config_path( self, tmp_path_factory): """Tests that the parent path in a config file is relative to that that configs own path (as opposed to the current working directory). """ # arrange root_dir = tmp_path_factory.mktemp("configs") parent_config_dir = root_dir / "parent_config_dir" child_config_dir = root_dir / "child_config_dir" parent_config_dir.mkdir() child_config_dir.mkdir() key = "darth" value = "vader" parent_config = plug.Config(parent_config_dir / "base-config.ini") parent_config[plug.Config.CORE_SECTION_NAME][key] = value parent_config.store() child_config = plug.Config(child_config_dir / "config.ini") child_config[plug.Config.CORE_SECTION_NAME][ plug.Config.PARENT_CONFIG_KEY] = str( pathlib.Path("..") / parent_config.path.relative_to(root_dir)) child_config.store() fetched_value = None class HandleConfig(plug.Plugin): def handle_config(self, config: plug.Config) -> None: nonlocal fetched_value fetched_value = config.get(plug.Config.CORE_SECTION_NAME, key) # act repobee.run( list(plug.cli.CoreCommand.config.show.as_name_tuple()), config_file=child_config.path, plugins=[HandleConfig], ) # assert assert fetched_value == value
def create_parser_for_docs() -> argparse.ArgumentParser: """Create a parser showing all options for the default CLI documentation. Returns: The primary parser, specifically for generating documentation. """ plugin.initialize_default_plugins() plugin.initialize_dist_plugins(force=True) return create_parser( config=plug.Config(_repobee.constants.DEFAULT_CONFIG_FILE))
def test_retains_values_that_are_not_specified( config_mock, defaults_options, select_repobee_section ): """Test that previous default values are retained if the option is skipped, and that plugin sections are not touched. """ # arrange confparser = configparser.ConfigParser() confparser.read(str(config_mock)) # add plugin section plugin_section = "junit4" plugin_options = collections.OrderedDict( (option, c) for option, c in zip( ["hamcrest_path", "junit_path"], ["/path/to/hamcrest", "/path/to/junit"], ) ) confparser.add_section(plugin_section) for option, value in plugin_options.items(): confparser[plugin_section][option] = value with open( str(config_mock), "w", encoding=sys.getdefaultencoding() ) as file: confparser.write(file) # remove an option and save expected retained value empty_option = list(defaults_options.keys())[3] defaults_options[empty_option] = "" expected_retained_default = confparser[plug.Config.CORE_SECTION_NAME][ empty_option ] # act with patch("builtins.input", side_effect=list(defaults_options.values())): configwizard.callback(None, plug.Config(config_mock)) # assert del defaults_options[empty_option] parser = configparser.ConfigParser() parser.read(str(config_mock)) assert ( parser[plug.Config.CORE_SECTION_NAME][empty_option] == expected_retained_default ) for option, value in defaults_options.items(): assert parser[plug.Config.CORE_SECTION_NAME][option] == value for option, value in plugin_options.items(): assert parser[plugin_section][option] == value
def test_enters_values_if_config_file_exists( config_mock, defaults_options, select_repobee_section ): """If the config file exists, a prompt should appear, and if the user enters yes the wizard should proceed as usuall. """ with patch("builtins.input", side_effect=list(defaults_options.values())): configwizard.callback(None, plug.Config(config_mock)) confparser = configparser.ConfigParser() confparser.read(str(config_mock)) for key, value in defaults_options.items(): assert confparser[plug.Config.CORE_SECTION_NAME][key] == value
def execute_config_hooks(config_file: Union[str, pathlib.Path]) -> None: """Execute all config hooks. Args: config_file: path to the config file. """ config_file = pathlib.Path(config_file) plug.manager.hook.handle_config(config=plug.Config(config_file)) if not config_file.is_file(): return config_parser = _read_config(config_file) plug.manager.hook.config_hook( config_parser=config_parser ) # TODO remove by 3.8.0
def test_handle_config_hook_recieves_config_with_inherited_properties( self, tmp_path_factory): first_tmpdir = tmp_path_factory.mktemp("configs") second_tmpdir = tmp_path_factory.mktemp("other-configs") section_name = "repobee" parent_key = "template_org_name" parent_value = "some-value" child_key = "org_name" child_value = "something" parent_config = plug.Config(second_tmpdir / "base-config.ini") parent_config[section_name][parent_key] = parent_value parent_config.store() child_config = plug.Config(first_tmpdir / "config.ini") child_config[section_name][child_key] = child_value child_config.parent = parent_config child_config.store() fetched_child_value = None fetched_parent_value = None class HandleConfig(plug.Plugin): def handle_config(self, config: plug.Config) -> None: nonlocal fetched_child_value, fetched_parent_value fetched_child_value = config.get(section_name, child_key) fetched_parent_value = config.get(section_name, parent_key) repobee.run( list(plug.cli.CoreCommand.config.show.as_name_tuple()), config_file=child_config.path, plugins=[HandleConfig], ) assert fetched_child_value == child_value assert fetched_parent_value == parent_value
def test_enters_values_if_no_config_exists( config_mock, defaults_options, select_repobee_section ): """If no config mock can be found (ensured by the nothing_exists fixture), then the config wizard chould proceed without prompting for a continue. """ with patch( "builtins.input", side_effect=list(defaults_options.values()) ), patch("pathlib.Path.exists", autospec=True, return_value=False): configwizard.callback(None, plug.Config(config_mock)) confparser = configparser.ConfigParser() confparser.read(str(config_mock)) for key, value in defaults_options.items(): assert confparser[plug.Config.CORE_SECTION_NAME][key] == value
def test_respects_config_file_argument(self, platform_url, tmp_path): # arrange config_file = tmp_path / "repobee.ini" unlikely_value = "badabimbadabum" # act with mock.patch( "bullet.Bullet.launch", autospec=True, return_value=_repobee.constants.CORE_SECTION_HDR, ), mock.patch("builtins.input", return_value=unlikely_value): _repobee.main.main( shlex.split( f"repobee --config-file {config_file} config wizard")) # assert config = plug.Config(config_file) assert (config.get(_repobee.constants.CORE_SECTION_HDR, "students_file") == unlikely_value)
def test_with_no_config_file(self, unused_path, plugin_manager_mock): config.execute_config_hooks(config=plug.Config(unused_path)) assert not plugin_manager_mock.hook.config_hook.called
def full_config(config_mock): # TODO remove use of config_mock here return plug.Config(pathlib.Path(config_mock))
def config_for_tests(empty_config_mock): """Returns the Config used in the tests and by other fixtures.""" return plug.Config(empty_config_mock)
def empty_config(tmp_path_factory): path = tmp_path_factory.mktemp("dir") / "config.ini" return plug.Config(path)
def _to_config(config_file: pathlib.Path) -> plug.Config: if config_file.is_file(): _repobee.config.check_config_integrity(config_file) return plug.Config(config_file)
def command(self): """The RepoBee "init-course" command hook method.""" inform("Initializing a Canvas course for use with RepoBee") # Step 1. Collecting information about the course on Canvas and Git. if _valid_git_setup(self._config): repobee_user = self._config[REPOBEE][REPOBEE_USER] repobee_base_url = self._config[REPOBEE][REPOBEE_BASE_URL] repobee_token = self._config[REPOBEE][REPOBEE_TOKEN] repobee_template_org_name = self._config[REPOBEE][ REPOBEE_TEMPLATE_ORG_NAME] repobee_org_name = self._config[REPOBEE][REPOBEE_ORG_NAME] if ask_closed( f"Use existing Git setup for user '{repobee_user}'? "): repobee_token = self._config[REPOBEE][REPOBEE_TOKEN] else: repobee_base_url = ask_open("Enter the Git base URL: ", repobee_base_url) repobee_user = ask_open("Enter your Git username: "******"Enter your Git access token: ") else: raise ValueError( ("Cannot find a working Git setup in your RepoBee " "configuration. Please, run `repobee config wizard` to " "configure git. See " "https://docs.repobee.org/en/stable/getting_started.html " "for more information.")) repobee_template_org_name = ask_open( ("Enter the template organization containing the " "template repositories used in this course: "), default=repobee_template_org_name) repobee_org_name = ask_open( ("Enter the target organization that is to contain the " "student repositories used in this course's instance: "), default=repobee_org_name) course_id = _extract_course_id(self.course_url) canvas_api_url = _extract_api_url(self.course_url) if _valid_canvas_setup(self._config, canvas_api_url): canvas_access_token = self._config[CANVAS][CANVAS_TOKEN] else: canvas_access_token = ask_open("Enter your Canvas access token: ") CanvasAPI().setup(canvas_api_url, canvas_access_token) course = Course.load(course_id) course_dir = ask_dir("Enter course directory name: ", str_to_path(course.name)) mapping_table = canvas_git_map_table_wizard(course) if mapping_table.empty(): warn("Canvas-Git mapping table CSV file is not created.") invalid_rows = [ r for r in mapping_table.rows() if not r[CANVAS_ID] or not r[GIT_ID] ] if len(invalid_rows) > 0: warn((f"{len(invalid_rows)} students do not have a Canvas or " "Git login ID. Please resolve this issue before using " "RepoBee to manage assignments for this course.")) # Step 2. Creating and filling a course directory with a Canvas-Git mapping # table and a RepoBee configuration file. inform("") inform(f"Created directory : {course_dir}") Path(course_dir).mkdir() if not mapping_table.empty(): path = f"{course_dir}/{CANVAS_GIT_MAP_FILENAME}" inform( f"Created file : {path} ⇝ the Canvas-Git mapping table CSV file" ) mapping_table.write(Path(path)) path = f"{course_dir}/{REPOBEE_CONFIG_FILENAME}" inform( f"Created file : {path} ⇝ the RepoBee configuration file" ) repobee_config = plug.Config(Path(path)) try: repobee_config.create_section(REPOBEE) except Exception as err: print(err) repobee_config[REPOBEE][REPOBEE_BASE_URL] = repobee_base_url repobee_config[REPOBEE][REPOBEE_USER] = repobee_user repobee_config[REPOBEE][REPOBEE_TOKEN] = repobee_token repobee_config[REPOBEE][ REPOBEE_TEMPLATE_ORG_NAME] = repobee_template_org_name repobee_config[REPOBEE][REPOBEE_ORG_NAME] = repobee_org_name try: repobee_config.create_section(CANVAS) except Exception as err: print(err) repobee_config[CANVAS][CANVAS_TOKEN] = canvas_access_token repobee_config[CANVAS][CANVAS_API_URL] = urlunparse(canvas_api_url) repobee_config[CANVAS][CANVAS_COURSE_ID] = str(course_id) repobee_config[CANVAS][CANVAS_GIT_MAP] = CANVAS_GIT_MAP_FILENAME repobee_config.store() inform(f"\nInitialization course '{course.name}' complete!")