Fix CI/CD: Add Gitea Actions workflow and fix linting issues

This commit is contained in:
Developer
2026-02-05 09:02:49 +00:00
commit d8325c4be2
111 changed files with 19657 additions and 0 deletions

203
tests/conftest.py Normal file
View File

@@ -0,0 +1,203 @@
"""Test configuration and fixtures."""
import pytest
from pathlib import Path
@pytest.fixture
def create_python_project(tmp_path: Path) -> Path:
"""Create a Python project structure for testing."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(src_dir / "__init__.py").write_text('"""Package init."""')
(src_dir / "main.py").write_text('''"""Main module."""
def hello():
"""Say hello."""
print("Hello, World!")
class Calculator:
"""A simple calculator."""
def add(self, a: int, b: int) -> int:
"""Add two numbers."""
return a + b
def multiply(self, a: int, b: int) -> int:
"""Multiply two numbers."""
return a * b
''')
(tmp_path / "requirements.txt").write_text('''requests>=2.31.0
click>=8.0.0
pytest>=7.0.0
''')
(tmp_path / "pyproject.toml").write_text('''[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "test-project"
version = "0.1.0"
description = "A test project"
requires-python = ">=3.9"
dependencies = [
"requests>=2.31.0",
"click>=8.0.0",
]
''')
return tmp_path
@pytest.fixture
def create_javascript_project(tmp_path: Path) -> Path:
"""Create a JavaScript project structure for testing."""
(tmp_path / "package.json").write_text('''{
"name": "test-js-project",
"version": "1.0.0",
"description": "A test JavaScript project",
"main": "index.js",
"dependencies": {
"express": "^4.18.0",
"lodash": "^4.17.0"
},
"devDependencies": {
"jest": "^29.0.0"
}
}
''')
(tmp_path / "index.js").write_text('''const express = require('express');
const _ = require('lodash');
function hello() {
return 'Hello, World!';
}
class Calculator {
add(a, b) {
return a + b;
}
}
module.exports = { hello, Calculator };
''')
return tmp_path
@pytest.fixture
def create_go_project(tmp_path: Path) -> Path:
"""Create a Go project structure for testing."""
(tmp_path / "go.mod").write_text('''module test-go-project
go 1.21
require (
github.com/gin-gonic/gin v1.9.0
github.com/stretchr/testify v1.8.0
)
''')
(tmp_path / "main.go").write_text('''package main
import "fmt"
func hello() string {
return "Hello, World!"
}
type Calculator struct{}
func (c *Calculator) Add(a, b int) int {
return a + b
}
func main() {
fmt.Println(hello())
}
''')
return tmp_path
@pytest.fixture
def create_rust_project(tmp_path: Path) -> Path:
"""Create a Rust project structure for testing."""
src_dir = tmp_path / "src"
src_dir.mkdir()
(tmp_path / "Cargo.toml").write_text('''[package]
name = "test-rust-project"
version = "0.1.0"
edition = "2021"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = "full" }
[dev-dependencies]
assertions = "0.1"
''')
(src_dir / "main.rs").write_text('''fn hello() -> String {
"Hello, World!".to_string()
}
pub struct Calculator;
impl Calculator {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
fn main() {
println!("{}", hello());
}
''')
return tmp_path
@pytest.fixture
def create_mixed_project(tmp_path: Path) -> Path:
"""Create a project with multiple languages for testing."""
python_part = tmp_path / "python_part"
python_part.mkdir()
js_part = tmp_path / "js_part"
js_part.mkdir()
src_dir = python_part / "src"
src_dir.mkdir()
(src_dir / "__init__.py").write_text('"""Package init."""')
(src_dir / "main.py").write_text('''"""Main module."""
def hello():
print("Hello")
''')
(python_part / "pyproject.toml").write_text('''[project]
name = "test-project"
version = "0.1.0"
description = "A test project"
requires-python = ">=3.9"
dependencies = ["requests>=2.31.0"]
''')
(js_part / "package.json").write_text('''{
"name": "test-js-project",
"version": "1.0.0",
"description": "A test JavaScript project"
}
''')
(js_part / "index.js").write_text('''function hello() {
return 'Hello';
}
module.exports = { hello };
''')
return tmp_path

75
tests/fixtures/README_EXAMPLE.md vendored Normal file
View File

@@ -0,0 +1,75 @@
# Example Generated README
This is an example of a README file that can be generated by Auto README Generator CLI.
## Overview
A Python project located at `/example/project` containing multiple files.
## Supported Languages
This project uses:
- **Python**
## Installation
```bash
pip install -r requirements.txt
pip install -e .
```
### Dependencies
- `requests` v2.31.0
- `click` v8.0.0
## Usage
### Basic Usage
```python
from project_name import main
main()
```
## Features
- Test suite included
- Uses 2 dependencies
- Contains 1 classes
- Contains 3 functions
## API Reference
### `hello()`
Say hello.
**Parameters:**
### `add(a, b)`
Add two numbers.
**Parameters:** `a, b`
## Contributing
1. Fork the repository
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
For Python development:
- Run tests with `pytest`
- Format code with `black` and `isort`
- Check types with `mypy`
## License
MIT
---
*Generated by Auto README Generator on 2024-01-15*

255
tests/test_analyzers.py Normal file
View File

@@ -0,0 +1,255 @@
"""Tests for code analyzers."""
import tempfile
from pathlib import Path
from src.auto_readme.analyzers import (
PythonAnalyzer,
JavaScriptAnalyzer,
GoAnalyzer,
RustAnalyzer,
CodeAnalyzerFactory,
)
class TestPythonAnalyzer:
"""Tests for PythonAnalyzer."""
def test_can_analyze_py_file(self):
"""Test that analyzer recognizes Python files."""
analyzer = PythonAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="w") as f:
f.write("def hello():\n pass")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_can_analyze_pyi_file(self):
"""Test that analyzer recognizes .pyi stub files."""
analyzer = PythonAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".pyi", delete=False, mode="w") as f:
f.write("def hello() -> None: ...")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_analyze_simple_function(self):
"""Test analyzing a simple function."""
with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="w") as f:
f.write('''"""Module docstring."""
def hello():
"""Say hello."""
print("Hello, World!")
class Calculator:
"""A calculator class."""
def add(self, a, b):
"""Add two numbers."""
return a + b
''')
f.flush()
file_path = Path(f.name)
analyzer = PythonAnalyzer()
result = analyzer.analyze(file_path)
assert len(result["functions"]) == 2 # hello and add
assert len(result["classes"]) == 1
hello_func = next((f for f in result["functions"] if f.name == "hello"), None)
assert hello_func is not None
assert hello_func.docstring == "Say hello."
assert hello_func.parameters == []
calc_class = result["classes"][0]
assert calc_class.name == "Calculator"
assert calc_class.docstring == "A calculator class."
Path(f.name).unlink()
def test_analyze_with_parameters(self):
"""Test analyzing functions with parameters."""
with tempfile.NamedTemporaryFile(suffix=".py", delete=False, mode="w") as f:
f.write('''def greet(name, greeting="Hello"):
"""Greet someone."""
return f"{greeting}, {name}!"
def add_numbers(a: int, b: int) -> int:
"""Add two integers."""
return a + b
''')
f.flush()
file_path = Path(f.name)
analyzer = PythonAnalyzer()
result = analyzer.analyze(file_path)
greet_func = next((f for f in result["functions"] if f.name == "greet"), None)
assert greet_func is not None
assert "name" in greet_func.parameters
assert "greeting" in greet_func.parameters
Path(f.name).unlink()
class TestJavaScriptAnalyzer:
"""Tests for JavaScriptAnalyzer."""
def test_can_analyze_js_file(self):
"""Test that analyzer recognizes JavaScript files."""
analyzer = JavaScriptAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".js", delete=False, mode="w") as f:
f.write("function hello() { return 'hello'; }")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_can_analyze_ts_file(self):
"""Test that analyzer recognizes TypeScript files."""
analyzer = JavaScriptAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".ts", delete=False, mode="w") as f:
f.write("const hello = (): string => 'hello';")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_analyze_simple_function(self):
"""Test analyzing a simple JavaScript function."""
with tempfile.NamedTemporaryFile(suffix=".js", delete=False, mode="w") as f:
f.write('''function hello(name) {
return "Hello, " + name + "!";
}
class Calculator {
add(a, b) {
return a + b;
}
}
module.exports = { hello, Calculator };
''')
f.flush()
file_path = Path(f.name)
analyzer = JavaScriptAnalyzer()
result = analyzer.analyze(file_path)
assert len(result["functions"]) >= 1
hello_func = next((f for f in result["functions"] if f.name == "hello"), None)
assert hello_func is not None
Path(f.name).unlink()
class TestGoAnalyzer:
"""Tests for GoAnalyzer."""
def test_can_analyze_go_file(self):
"""Test that analyzer recognizes Go files."""
analyzer = GoAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".go", delete=False, mode="w") as f:
f.write("package main\n\nfunc hello() string { return 'hello' }")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_analyze_simple_function(self):
"""Test analyzing a simple Go function."""
with tempfile.NamedTemporaryFile(suffix=".go", delete=False, mode="w") as f:
f.write('''package main
import "fmt"
func hello(name string) string {
return "Hello, " + name
}
type Calculator struct{}
func (c *Calculator) Add(a, b int) int {
return a + b
}
''')
f.flush()
file_path = Path(f.name)
analyzer = GoAnalyzer()
result = analyzer.analyze(file_path)
hello_func = next((f for f in result["functions"] if f.name == "hello"), None)
assert hello_func is not None
assert hello_func.return_type is not None or "string" in str(hello_func.return_type)
Path(f.name).unlink()
class TestRustAnalyzer:
"""Tests for RustAnalyzer."""
def test_can_analyze_rs_file(self):
"""Test that analyzer recognizes Rust files."""
analyzer = RustAnalyzer()
with tempfile.NamedTemporaryFile(suffix=".rs", delete=False, mode="w") as f:
f.write("fn hello() -> String { String::from('hello') }")
f.flush()
assert analyzer.can_analyze(Path(f.name))
Path(f.name).unlink()
def test_analyze_simple_function(self):
"""Test analyzing a simple Rust function."""
with tempfile.NamedTemporaryFile(suffix=".rs", delete=False, mode="w") as f:
f.write('''fn hello(name: &str) -> String {
format!("Hello, {}", name)
}
pub struct Calculator;
impl Calculator {
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
}
''')
f.flush()
file_path = Path(f.name)
analyzer = RustAnalyzer()
result = analyzer.analyze(file_path)
hello_func = next((f for f in result["functions"] if f.name == "hello"), None)
assert hello_func is not None
assert "name" in hello_func.parameters
assert hello_func.visibility == "private"
Path(f.name).unlink()
class TestCodeAnalyzerFactory:
"""Tests for CodeAnalyzerFactory."""
def test_get_analyzer_python(self):
"""Test getting analyzer for Python file."""
analyzer = CodeAnalyzerFactory.get_analyzer(Path("main.py"))
assert isinstance(analyzer, PythonAnalyzer)
def test_get_analyzer_js(self):
"""Test getting analyzer for JavaScript file."""
analyzer = CodeAnalyzerFactory.get_analyzer(Path("index.js"))
assert isinstance(analyzer, JavaScriptAnalyzer)
def test_get_analyzer_go(self):
"""Test getting analyzer for Go file."""
analyzer = CodeAnalyzerFactory.get_analyzer(Path("main.go"))
assert isinstance(analyzer, GoAnalyzer)
def test_get_analyzer_rust(self):
"""Test getting analyzer for Rust file."""
analyzer = CodeAnalyzerFactory.get_analyzer(Path("main.rs"))
assert isinstance(analyzer, RustAnalyzer)
def test_can_analyze(self):
"""Test can_analyze returns correct results."""
assert CodeAnalyzerFactory.can_analyze(Path("main.py"))
assert CodeAnalyzerFactory.can_analyze(Path("index.js"))
assert CodeAnalyzerFactory.can_analyze(Path("main.go"))
assert CodeAnalyzerFactory.can_analyze(Path("main.rs"))
assert not CodeAnalyzerFactory.can_analyze(Path("random.txt"))

134
tests/test_cli.py Normal file
View File

@@ -0,0 +1,134 @@
"""Tests for CLI commands."""
from click.testing import CliRunner
from src.auto_readme.cli import generate, preview, analyze, init_config
class TestGenerateCommand:
"""Tests for the generate command."""
def test_generate_basic_python(self, create_python_project, tmp_path):
"""Test basic README generation for Python project."""
from src.auto_readme.cli import generate
runner = CliRunner()
result = runner.invoke(generate, ["--input", str(create_python_project), "--output", str(tmp_path / "README.md")])
assert result.exit_code == 0
readme_content = (tmp_path / "README.md").read_text()
assert "# test-project" in readme_content
def test_generate_dry_run(self, create_python_project):
"""Test README generation with dry-run option."""
runner = CliRunner()
result = runner.invoke(generate, ["--input", str(create_python_project), "--dry-run"])
assert result.exit_code == 0
assert "# test-project" in result.output
def test_generate_force_overwrite(self, create_python_project, tmp_path):
"""Test forced README overwrite."""
readme_file = tmp_path / "README.md"
readme_file.write_text("# Existing README")
runner = CliRunner()
result = runner.invoke(generate, ["--input", str(create_python_project), "--output", str(readme_file), "--force"])
assert result.exit_code == 0
assert readme_file.read_text() != "# Existing README"
def test_generate_with_template(self, create_python_project):
"""Test README generation with specific template."""
runner = CliRunner()
result = runner.invoke(generate, ["--input", str(create_python_project), "--template", "base", "--dry-run"])
assert result.exit_code == 0
class TestPreviewCommand:
"""Tests for the preview command."""
def test_preview_python_project(self, create_python_project):
"""Test previewing README for Python project."""
runner = CliRunner()
result = runner.invoke(preview, ["--input", str(create_python_project)])
assert result.exit_code == 0
assert "# test-project" in result.output
class TestAnalyzeCommand:
"""Tests for the analyze command."""
def test_analyze_python_project(self, create_python_project):
"""Test analyzing Python project."""
runner = CliRunner()
result = runner.invoke(analyze, [str(create_python_project)])
assert result.exit_code == 0
assert "test-project" in result.output
assert "Type: python" in result.output
def test_analyze_js_project(self, create_javascript_project):
"""Test analyzing JavaScript project."""
runner = CliRunner()
result = runner.invoke(analyze, [str(create_javascript_project)])
assert result.exit_code == 0
assert "Type: javascript" in result.output
def test_analyze_go_project(self, create_go_project):
"""Test analyzing Go project."""
runner = CliRunner()
result = runner.invoke(analyze, [str(create_go_project)])
assert result.exit_code == 0
assert "Type: go" in result.output
def test_analyze_rust_project(self, create_rust_project):
"""Test analyzing Rust project."""
runner = CliRunner()
result = runner.invoke(analyze, [str(create_rust_project)])
assert result.exit_code == 0
assert "Type: rust" in result.output
class TestInitConfigCommand:
"""Tests for the init-config command."""
def test_init_config(self, tmp_path):
"""Test generating configuration template."""
runner = CliRunner()
result = runner.invoke(init_config, ["--output", str(tmp_path / ".readmerc")])
assert result.exit_code == 0
config_content = (tmp_path / ".readmerc").read_text()
assert "project_name:" in config_content
assert "description:" in config_content
def test_init_config_default_path(self, tmp_path):
"""Test generating configuration at default path."""
import os
original_dir = os.getcwd()
try:
os.chdir(tmp_path)
runner = CliRunner()
result = runner.invoke(init_config, ["--output", ".readmerc"])
assert result.exit_code == 0
assert (tmp_path / ".readmerc").exists()
finally:
os.chdir(original_dir)

132
tests/test_config.py Normal file
View File

@@ -0,0 +1,132 @@
"""Tests for configuration loading."""
from pathlib import Path
import pytest
from src.auto_readme.config import ConfigLoader, ConfigValidator, ReadmeConfig
class TestConfigLoader:
"""Tests for ConfigLoader."""
def test_find_config_yaml(self, tmp_path):
"""Test finding YAML configuration file."""
(tmp_path / ".readmerc.yaml").write_text("project_name: test")
config_path = ConfigLoader.find_config(tmp_path)
assert config_path is not None
assert config_path.name == ".readmerc.yaml"
def test_find_config_yml(self, tmp_path):
"""Test finding YML configuration file."""
(tmp_path / ".readmerc.yml").write_text("project_name: test")
config_path = ConfigLoader.find_config(tmp_path)
assert config_path is not None
def test_find_config_toml(self, tmp_path):
"""Test finding TOML configuration file."""
(tmp_path / ".readmerc").write_text("project_name = 'test'")
config_path = ConfigLoader.find_config(tmp_path)
assert config_path is not None
def test_load_yaml_config(self, tmp_path):
"""Test loading YAML configuration file."""
config_file = tmp_path / ".readmerc.yaml"
config_file.write_text("""
project_name: "My Test Project"
description: "A test description"
template: "minimal"
interactive: true
sections:
order:
- title
- description
- overview
custom_fields:
author: "Test Author"
email: "test@example.com"
""")
config = ConfigLoader.load(config_file)
assert config.project_name == "My Test Project"
assert config.description == "A test description"
assert config.template == "minimal"
assert config.interactive is True
assert "author" in config.custom_fields
def test_load_toml_config(self, tmp_path):
"""Test loading TOML configuration file."""
config_file = tmp_path / "pyproject.toml"
config_file.write_text("""
[tool.auto-readme]
filename = "README.md"
sections = ["title", "description", "overview"]
""")
config = ConfigLoader.load(config_file)
assert config.output_filename == "README.md"
def test_load_nonexistent_file(self):
"""Test loading nonexistent configuration file."""
config = ConfigLoader.load(Path("/nonexistent/config.yaml"))
assert config.project_name is None
def test_load_invalid_yaml(self, tmp_path):
"""Test loading invalid YAML raises error."""
config_file = tmp_path / ".readmerc.yaml"
config_file.write_text("invalid: yaml: content: [")
with pytest.raises(ValueError):
ConfigLoader.load(config_file)
class TestConfigValidator:
"""Tests for ConfigValidator."""
def test_validate_valid_config(self):
"""Test validating a valid configuration."""
config = ReadmeConfig(
project_name="test",
template="base",
)
errors = ConfigValidator.validate(config)
assert len(errors) == 0
def test_validate_invalid_template(self):
"""Test validating invalid template name."""
config = ReadmeConfig(
project_name="test",
template="nonexistent",
)
errors = ConfigValidator.validate(config)
assert len(errors) == 1
assert "Invalid template" in errors[0]
def test_validate_invalid_section(self):
"""Test validating invalid section name."""
config = ReadmeConfig(
project_name="test",
sections={"order": ["invalid_section"]},
)
errors = ConfigValidator.validate(config)
assert len(errors) == 1
assert "Invalid section" in errors[0]
def test_generate_template(self):
"""Test generating configuration template."""
template = ConfigValidator.generate_template()
assert "project_name:" in template
assert "description:" in template
assert "template:" in template
assert "interactive:" in template

242
tests/test_parsers.py Normal file
View File

@@ -0,0 +1,242 @@
"""Tests for dependency parsers."""
import tempfile
from pathlib import Path
from src.auto_readme.parsers import (
PythonDependencyParser,
JavaScriptDependencyParser,
GoDependencyParser,
RustDependencyParser,
DependencyParserFactory,
)
class TestPythonDependencyParser:
"""Tests for PythonDependencyParser."""
def test_can_parse_requirements_txt(self):
"""Test that parser recognizes requirements.txt."""
parser = PythonDependencyParser()
with tempfile.TemporaryDirectory() as tmp_dir:
req_file = Path(tmp_dir) / "requirements.txt"
req_file.write_text("requests>=2.31.0\n")
assert parser.can_parse(req_file)
def test_can_parse_pyproject_toml(self):
"""Test that parser recognizes pyproject.toml."""
parser = PythonDependencyParser()
with tempfile.TemporaryDirectory() as tmp_dir:
pyproject_file = Path(tmp_dir) / "pyproject.toml"
pyproject_file.write_text('[project]\ndependencies = ["requests>=2.0"]')
assert parser.can_parse(pyproject_file)
def test_parse_requirements_txt(self):
"""Test parsing requirements.txt file."""
with tempfile.TemporaryDirectory() as tmp_dir:
req_file = Path(tmp_dir) / "requirements.txt"
req_file.write_text("""
requests>=2.31.0
click>=8.0.0
pytest==7.0.0
-e git+https://github.com/user/repo.git#egg=package
# This is a comment
numpy~=1.24.0
""")
parser = PythonDependencyParser()
deps = parser.parse(req_file)
assert len(deps) == 5
names = {d.name for d in deps}
assert "requests" in names
assert "click" in names
assert "pytest" in names
assert "numpy" in names
def test_parse_pyproject_toml(self):
"""Test parsing pyproject.toml file."""
with tempfile.TemporaryDirectory() as tmp_dir:
pyproject_file = Path(tmp_dir) / "pyproject.toml"
pyproject_file.write_text("""
[project]
name = "test-project"
version = "0.1.0"
dependencies = [
"requests>=2.31.0",
"click>=8.0.0",
]
[project.optional-dependencies]
dev = ["pytest>=7.0.0", "black>=23.0.0"]
""")
parser = PythonDependencyParser()
deps = parser.parse(pyproject_file)
assert len(deps) == 4
class TestJavaScriptDependencyParser:
"""Tests for JavaScriptDependencyParser."""
def test_can_parse_package_json(self):
"""Test that parser recognizes package.json."""
parser = JavaScriptDependencyParser()
with tempfile.TemporaryDirectory() as tmp_dir:
package_file = Path(tmp_dir) / "package.json"
package_file.write_text('{"name": "test"}')
assert parser.can_parse(package_file)
def test_parse_package_json(self):
"""Test parsing package.json file."""
with tempfile.TemporaryDirectory() as tmp_dir:
package_file = Path(tmp_dir) / "package.json"
package_file.write_text("""
{
"name": "test-project",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0",
"lodash": "~4.17.0"
},
"devDependencies": {
"jest": "^29.0.0"
},
"optionalDependencies": {
"bcrypt": "^5.0.0"
}
}
""")
parser = JavaScriptDependencyParser()
deps = parser.parse(package_file)
assert len(deps) == 4
express = next((d for d in deps if d.name == "express"), None)
assert express is not None
assert express.version == "4.18.0"
assert not express.is_dev
jest = next((d for d in deps if d.name == "jest"), None)
assert jest is not None
assert jest.is_dev
bcrypt = next((d for d in deps if d.name == "bcrypt"), None)
assert bcrypt is not None
assert bcrypt.is_optional
class TestGoDependencyParser:
"""Tests for GoDependencyParser."""
def test_can_parse_go_mod(self):
"""Test that parser recognizes go.mod."""
parser = GoDependencyParser()
with tempfile.TemporaryDirectory() as tmp_dir:
go_mod_file = Path(tmp_dir) / "go.mod"
go_mod_file.write_text("module test\n")
assert parser.can_parse(go_mod_file)
def test_parse_go_mod(self):
"""Test parsing go.mod file."""
with tempfile.TemporaryDirectory() as tmp_dir:
go_mod_file = Path(tmp_dir) / "go.mod"
go_mod_file.write_text("""
module test-go-project
go 1.21
require (
github.com/gin-gonic/gin v1.9.0
github.com/stretchr/testify v1.8.0
)
require github.com/user/repo v1.0.0
""")
parser = GoDependencyParser()
deps = parser.parse(go_mod_file)
assert len(deps) == 3
gin = next((d for d in deps if d.name == "github.com/gin-gonic/gin"), None)
assert gin is not None
assert gin.version is not None and "1.9.0" in gin.version
class TestRustDependencyParser:
"""Tests for RustDependencyParser."""
def test_can_parse_cargo_toml(self):
"""Test that parser recognizes Cargo.toml."""
parser = RustDependencyParser()
with tempfile.TemporaryDirectory() as tmp_dir:
cargo_file = Path(tmp_dir) / "Cargo.toml"
cargo_file.write_text('[package]\nname = "test"')
assert parser.can_parse(cargo_file)
def test_parse_cargo_toml(self):
"""Test parsing Cargo.toml file."""
with tempfile.TemporaryDirectory() as tmp_dir:
cargo_file = Path(tmp_dir) / "Cargo.toml"
cargo_file.write_text("""
[package]
name = "test-project"
version = "0.1.0"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
[dev-dependencies]
assertions = "0.1"
""")
parser = RustDependencyParser()
deps = parser.parse(cargo_file)
assert len(deps) == 3
serde = next((d for d in deps if d.name == "serde"), None)
assert serde is not None
assert serde.version is not None and "1.0" in serde.version
assert not serde.is_dev
assertions = next((d for d in deps if d.name == "assertions"), None)
assert assertions is not None
assert assertions.is_dev
class TestDependencyParserFactory:
"""Tests for DependencyParserFactory."""
def test_get_parser_python(self):
"""Test getting parser for Python file."""
parser = DependencyParserFactory.get_parser(Path("requirements.txt"))
assert isinstance(parser, PythonDependencyParser)
def test_get_parser_js(self):
"""Test getting parser for JavaScript file."""
parser = DependencyParserFactory.get_parser(Path("package.json"))
assert isinstance(parser, JavaScriptDependencyParser)
def test_get_parser_go(self):
"""Test getting parser for Go file."""
parser = DependencyParserFactory.get_parser(Path("go.mod"))
assert isinstance(parser, GoDependencyParser)
def test_get_parser_rust(self):
"""Test getting parser for Rust file."""
parser = DependencyParserFactory.get_parser(Path("Cargo.toml"))
assert isinstance(parser, RustDependencyParser)
def test_can_parse(self):
"""Test can_parse returns correct results."""
assert DependencyParserFactory.can_parse(Path("requirements.txt"))
assert DependencyParserFactory.can_parse(Path("package.json"))
assert DependencyParserFactory.can_parse(Path("go.mod"))
assert DependencyParserFactory.can_parse(Path("Cargo.toml"))
assert not DependencyParserFactory.can_parse(Path("random.txt"))

197
tests/test_templates.py Normal file
View File

@@ -0,0 +1,197 @@
"""Tests for template rendering."""
import tempfile
from pathlib import Path
from src.auto_readme.models import Project, ProjectConfig, ProjectType
from src.auto_readme.templates import TemplateRenderer, TemplateManager
class TestTemplateRenderer:
"""Tests for TemplateRenderer."""
def test_render_base_template(self):
"""Test rendering the base template."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
config=ProjectConfig(
name="test-project",
description="A test project",
),
)
renderer = TemplateRenderer()
content = renderer.render(project, "base")
assert "# test-project" in content
assert "A test project" in content
assert "## Overview" in content
def test_render_minimal_template(self):
"""Test rendering the minimal template."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
)
renderer = TemplateRenderer()
content = renderer.render(project, "base")
assert "Generated by Auto README Generator" in content
def test_tech_stack_detection(self):
"""Test technology stack detection."""
from src.auto_readme.models import Dependency
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
dependencies=[
Dependency(name="fastapi"),
Dependency(name="flask"),
Dependency(name="requests"),
],
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "FastAPI" in context["tech_stack"]
assert "Flask" in context["tech_stack"]
def test_installation_steps_generation(self):
"""Test automatic installation steps generation."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "pip install" in context["installation_steps"][0]
def test_go_installation_steps(self):
"""Test Go installation steps."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.GO,
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "go mod download" in context["installation_steps"][0]
def test_rust_installation_steps(self):
"""Test Rust installation steps."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.RUST,
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "cargo build" in context["installation_steps"][0]
def test_feature_detection(self):
"""Test automatic feature detection."""
from src.auto_readme.models import Function, Class, SourceFile, FileType
functions = [
Function(name="test_func", line_number=1),
]
classes = [
Class(name="TestClass", line_number=1),
]
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
files=[
SourceFile(
path=Path("test.py"),
file_type=FileType.SOURCE,
functions=functions,
classes=classes,
),
],
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "Contains 1 classes" in context["features"]
assert "Contains 1 functions" in context["features"]
def test_custom_context_override(self):
"""Test custom context can override auto-detected values."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
)
renderer = TemplateRenderer()
content = renderer.render(
project,
"base",
title="Custom Title",
description="Custom description",
)
assert "# Custom Title" in content
assert "Custom description" in content
def test_contributing_guidelines(self):
"""Test contributing guidelines generation."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.PYTHON,
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "Fork the repository" in context["contributing_guidelines"]
assert "git checkout -b" in context["contributing_guidelines"]
assert "pytest" in context["contributing_guidelines"]
def test_javascript_contributing_guidelines(self):
"""Test JavaScript contributing guidelines."""
project = Project(
root_path=Path("/test"),
project_type=ProjectType.JAVASCRIPT,
)
renderer = TemplateRenderer()
context = renderer._build_context(project)
assert "npm test" in context["contributing_guidelines"]
class TestTemplateManager:
"""Tests for TemplateManager."""
def test_list_templates(self):
"""Test listing available templates."""
manager = TemplateManager()
templates = manager.list_templates()
assert "base" in templates
def test_get_template_path_builtin(self):
"""Test getting path for built-in template."""
manager = TemplateManager()
path = manager.get_template_path("base")
assert path is None
def test_get_template_path_custom(self):
"""Test getting path for custom template."""
with tempfile.TemporaryDirectory() as tmp_dir:
manager = TemplateManager(Path(tmp_dir))
path = manager.get_template_path("custom.md.j2")
assert path is not None

132
tests/test_utils.py Normal file
View File

@@ -0,0 +1,132 @@
"""Tests for utility modules."""
import tempfile
from pathlib import Path
from src.auto_readme.utils.path_utils import PathUtils
from src.auto_readme.utils.file_scanner import FileScanner
class TestPathUtils:
"""Tests for PathUtils."""
def test_normalize_path(self):
"""Test path normalization."""
path = PathUtils.normalize_path("./foo/bar")
assert path.is_absolute()
def test_is_ignored_dir(self):
"""Test ignored directory detection."""
assert PathUtils.is_ignored_dir(Path("__pycache__"))
assert PathUtils.is_ignored_dir(Path(".git"))
assert PathUtils.is_ignored_dir(Path("node_modules"))
assert not PathUtils.is_ignored_dir(Path("src"))
def test_is_ignored_file(self):
"""Test ignored file detection."""
assert PathUtils.is_ignored_file(Path(".gitignore"))
assert not PathUtils.is_ignored_file(Path("main.py"))
def test_is_source_file(self):
"""Test source file detection."""
assert PathUtils.is_source_file(Path("main.py"))
assert PathUtils.is_source_file(Path("index.js"))
assert PathUtils.is_source_file(Path("main.go"))
assert PathUtils.is_source_file(Path("lib.rs"))
assert not PathUtils.is_source_file(Path("README.md"))
def test_is_config_file(self):
"""Test config file detection."""
assert PathUtils.is_config_file(Path("config.json"))
assert PathUtils.is_config_file(Path("settings.yaml"))
assert PathUtils.is_config_file(Path("pyproject.toml"))
def test_is_test_file(self):
"""Test test file detection."""
assert PathUtils.is_test_file(Path("test_main.py"))
assert PathUtils.is_test_file(Path("tests/utils.py"))
assert PathUtils.is_test_file(Path("example.test.js"))
assert not PathUtils.is_test_file(Path("main.py"))
def test_is_hidden(self):
"""Test hidden file detection."""
assert PathUtils.is_hidden(Path(".gitignore"))
assert not PathUtils.is_hidden(Path("main.py"))
def test_detect_project_root(self):
"""Test project root detection."""
with tempfile.TemporaryDirectory() as tmp_dir:
root = Path(tmp_dir)
subdir = root / "src" / "package"
subdir.mkdir(parents=True)
(root / "pyproject.toml").touch()
detected = PathUtils.detect_project_root(subdir)
assert detected == root
def test_get_relative_path(self):
"""Test relative path calculation."""
base = Path("/home/user/project")
target = Path("/home/user/project/src/main.py")
relative = PathUtils.get_relative_path(target, base)
assert relative == Path("src/main.py")
class TestFileScanner:
"""Tests for FileScanner."""
def test_scan_python_project(self, create_python_project):
"""Test scanning a Python project."""
scanner = FileScanner(create_python_project)
files = scanner.scan_and_create()
file_names = {f.path.name for f in files}
assert "main.py" in file_names
assert "__init__.py" in file_names
assert "requirements.txt" in file_names
assert "pyproject.toml" in file_names
def test_detect_project_type_python(self, create_python_project):
"""Test project type detection for Python."""
from src.auto_readme.models import ProjectType
scanner = FileScanner(create_python_project)
project_type = scanner.detect_project_type()
assert project_type == ProjectType.PYTHON
def test_detect_project_type_javascript(self, create_javascript_project):
"""Test project type detection for JavaScript."""
from src.auto_readme.models import ProjectType
scanner = FileScanner(create_javascript_project)
project_type = scanner.detect_project_type()
assert project_type == ProjectType.JAVASCRIPT
def test_detect_project_type_go(self, create_go_project):
"""Test project type detection for Go."""
from src.auto_readme.models import ProjectType
scanner = FileScanner(create_go_project)
project_type = scanner.detect_project_type()
assert project_type == ProjectType.GO
def test_detect_project_type_rust(self, create_rust_project):
"""Test project type detection for Rust."""
from src.auto_readme.models import ProjectType
scanner = FileScanner(create_rust_project)
project_type = scanner.detect_project_type()
assert project_type == ProjectType.RUST
def test_scan_mixed_project(self, create_mixed_project):
"""Test scanning a project with multiple languages."""
scanner = FileScanner(create_mixed_project)
files = scanner.scan_and_create()
assert len(files) > 0