|
| 1 | +from pathlib import Path |
| 2 | + |
| 3 | +import pytest |
| 4 | +from click import UsageError |
| 5 | + |
| 6 | +import scripts.init as init |
| 7 | + |
| 8 | + |
| 9 | +def test_validate_inputs(): |
| 10 | + # Valid inputs should pass without exception |
| 11 | + init._validate_inputs( |
| 12 | + name="my-project", |
| 13 | + description="A cool project", |
| 14 | + author="Alice Smith", |
| 15 | + email="alice@example.com", |
| 16 | + github="alice-smith", |
| 17 | + ) |
| 18 | + |
| 19 | + # Test length validation (>100 characters) |
| 20 | + with pytest.raises(UsageError, match="Invalid name: maximum length is 100 characters"): |
| 21 | + init._validate_inputs("a" * 101, "desc", "author", "email@test.com", "gh") |
| 22 | + |
| 23 | + # Test non-printable characters validation |
| 24 | + with pytest.raises(UsageError, match="Invalid description: control characters are not allowed"): |
| 25 | + init._validate_inputs("name", "desc\x01", "author", "email@test.com", "gh") |
| 26 | + |
| 27 | + # Test double quote validation (not allowed in name/author/email/github) |
| 28 | + with pytest.raises(UsageError, match="Invalid author: double quotes are not allowed"): |
| 29 | + init._validate_inputs("name", "desc", 'author"with"quotes', "email@test.com", "gh") |
| 30 | + |
| 31 | + # Double quotes are allowed in description |
| 32 | + init._validate_inputs("name", 'description with "quotes"', "author", "email@test.com", "gh") |
| 33 | + |
| 34 | + # Test project name format |
| 35 | + with pytest.raises(UsageError, match="Invalid project name"): |
| 36 | + init._validate_inputs("Invalid_Project_Name!", "desc", "author", "email@test.com", "gh") |
| 37 | + |
| 38 | + # Test github username format |
| 39 | + with pytest.raises(UsageError, match="Invalid GitHub username"): |
| 40 | + init._validate_inputs("name", "desc", "author", "email@test.com", "invalid_username") |
| 41 | + |
| 42 | + # Test email format |
| 43 | + with pytest.raises(UsageError, match="Invalid email address"): |
| 44 | + init._validate_inputs("name", "desc", "author", "not-an-email", "gh") |
| 45 | + |
| 46 | + |
| 47 | +def test_get_git_config(monkeypatch): |
| 48 | + # Clear cache first to ensure a clean state |
| 49 | + monkeypatch.setattr(init, "_git_config_loaded", False) |
| 50 | + monkeypatch.setattr(init, "_git_config_cache", {}) |
| 51 | + |
| 52 | + # Mock subprocess.check_output |
| 53 | + def mock_check_output(args, **kwargs): |
| 54 | + if "config" in args and "--get-regexp" in args: |
| 55 | + return "user.name Bob Jones\nuser.email bob@example.com\ngithub.user bobjones\n" |
| 56 | + raise ValueError("Unexpected command") |
| 57 | + |
| 58 | + monkeypatch.setattr(init, "check_output", mock_check_output) |
| 59 | + |
| 60 | + assert init._get_git_config("user.name") == "Bob Jones" |
| 61 | + assert init._get_git_config("user.email") == "bob@example.com" |
| 62 | + assert init._get_git_config("github.user") == "bobjones" |
| 63 | + |
| 64 | + # Uncached custom key should invoke check_output directly |
| 65 | + monkeypatch.setattr(init, "check_output", lambda args, **kwargs: "custom-value" if "custom.key" in args else "") |
| 66 | + assert init._get_git_config("custom.key") == "custom-value" |
| 67 | + |
| 68 | + |
| 69 | +def test_get_default_github(monkeypatch): |
| 70 | + # Case 1: valid username in git config |
| 71 | + monkeypatch.setattr(init, "_git_config_loaded", True) |
| 72 | + monkeypatch.setattr(init, "_git_config_cache", {"github.user": "jane-doe"}) |
| 73 | + assert init._get_default_github() == "jane-doe" |
| 74 | + |
| 75 | + # Case 2: invalid username in git config, fallback to remote URL (HTTPS format) |
| 76 | + monkeypatch.setattr(init, "_git_config_cache", {}) |
| 77 | + |
| 78 | + def mock_check_output(args, **kwargs): |
| 79 | + if "remote" in args and "get-url" in args: |
| 80 | + return "https://github.com/some-org/some-repo.git\n" |
| 81 | + raise ValueError("Unexpected command") |
| 82 | + |
| 83 | + monkeypatch.setattr(init, "check_output", mock_check_output) |
| 84 | + assert init._get_default_github() == "some-org" |
| 85 | + |
| 86 | + # Case 3: invalid username in git config, fallback to remote URL (SSH format) |
| 87 | + def mock_check_output_ssh(args, **kwargs): |
| 88 | + if "remote" in args and "get-url" in args: |
| 89 | + return "git@github.com:ssh-user/some-repo.git\n" |
| 90 | + raise ValueError("Unexpected") |
| 91 | + |
| 92 | + monkeypatch.setattr(init, "check_output", mock_check_output_ssh) |
| 93 | + assert init._get_default_github() == "ssh-user" |
| 94 | + |
| 95 | + |
| 96 | +def test_perform_replacements(tmp_path, monkeypatch): |
| 97 | + # Change current working directory to tmp_path so the script modifies files there |
| 98 | + monkeypatch.chdir(tmp_path) |
| 99 | + |
| 100 | + # Set up mocked files that would be modified by the script |
| 101 | + docs_ref = tmp_path / "docs" / "reference" |
| 102 | + docs_ref.mkdir(parents=True) |
| 103 | + app_md = docs_ref / "app.md" |
| 104 | + app_md.write_text("::: project.app\nsome other content") |
| 105 | + |
| 106 | + mkdocs_yml = tmp_path / "mkdocs.yml" |
| 107 | + mkdocs_yml.write_text("repo_name: original/repo\nrepo_url: https://github.com/original/repo\n") |
| 108 | + |
| 109 | + pyproject_toml = tmp_path / "pyproject.toml" |
| 110 | + pyproject_toml.write_text( |
| 111 | + 'source = ["project"]\n' |
| 112 | + 'app = "project.app:main"\n' |
| 113 | + 'name = "project"\n' |
| 114 | + 'description = "original description"\n' |
| 115 | + 'authors = ["Original Author <email>"]\n' |
| 116 | + ) |
| 117 | + |
| 118 | + docs_readme = tmp_path / "docs" / "README.md" |
| 119 | + docs_readme.parent.mkdir(parents=True, exist_ok=True) |
| 120 | + docs_readme.write_text("# Old Description\nsome doc content") |
| 121 | + |
| 122 | + github_dir = tmp_path / ".github" |
| 123 | + github_dir.mkdir() |
| 124 | + codeowners = github_dir / "CODEOWNERS" |
| 125 | + codeowners.write_text("@original_owner") |
| 126 | + |
| 127 | + funding_yml = github_dir / "FUNDING.yml" |
| 128 | + funding_yml.write_text("github: [original_owner]") |
| 129 | + |
| 130 | + # Run perform replacements |
| 131 | + init._perform_replacements( |
| 132 | + source="my_new_source", |
| 133 | + github="new-github", |
| 134 | + name="my-new-name", |
| 135 | + description='New "escaped" Description', |
| 136 | + author="New Author", |
| 137 | + email="new@example.com", |
| 138 | + ) |
| 139 | + |
| 140 | + # Assertions to verify correct updates |
| 141 | + assert "::: my_new_source.app" in app_md.read_text() |
| 142 | + assert "repo_name: new-github/my-new-name" in mkdocs_yml.read_text() |
| 143 | + assert "repo_url: https://github.com/new-github/my-new-name" in mkdocs_yml.read_text() |
| 144 | + |
| 145 | + pyproject_content = pyproject_toml.read_text() |
| 146 | + assert 'source = ["my_new_source"]' in pyproject_content |
| 147 | + assert 'app = "my_new_source.app:main"' in pyproject_content |
| 148 | + assert 'name = "my_new_source"' in pyproject_content |
| 149 | + assert 'description = "New \\"escaped\\" Description"' in pyproject_content |
| 150 | + assert 'authors = [{name = "New Author", email = "new@example.com"}]' in pyproject_content |
| 151 | + |
| 152 | + assert '# New "escaped" Description' in docs_readme.read_text() |
| 153 | + assert "@new-github" in codeowners.read_text() |
| 154 | + assert "github: [new-github]" in funding_yml.read_text() |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + import pytest |
| 159 | + |
| 160 | + pytest.main() |
0 commit comments