diff --git a/tests/unit/test_parsers.py b/tests/unit/test_parsers.py new file mode 100644 index 0000000..36ae458 --- /dev/null +++ b/tests/unit/test_parsers.py @@ -0,0 +1,248 @@ +"""Tests for package parsers.""" + +import tempfile +import os +from pathlib import Path + +import pytest + +from depcheck.models import PackageManager +from depcheck.parsers import create_parser_registry +from depcheck.parsers.cargo import CargoParser +from depcheck.parsers.go import GoParser +from depcheck.parsers.npm import NpmParser +from depcheck.parsers.pip import PipParser + + +class TestNpmParser: + """Tests for NPM package.json parser.""" + + def test_parse_simple_package_json(self): + """Test parsing a simple package.json.""" + content = """{ + \"name\": \"test-project\", + \"version\": \"1.0.0\", + \"dependencies\": { + \"express\": \"4.18.2\", + \"lodash\": \"4.17.21\" + } + }""" + + with tempfile.TemporaryDirectory() as tmpdir: + pkg_path = Path(tmpdir) / "package.json" + pkg_path.write_text(content) + + parser = NpmParser() + deps = parser.parse(pkg_path) + + assert len(deps) == 2 + assert any(d.name == "express" for d in deps) + assert any(d.name == "lodash" for d in deps) + + def test_parse_dev_dependencies(self): + """Test parsing devDependencies.""" + content = """{ + \"name\": \"test-project\", + \"devDependencies\": { + \"jest\": \"29.7.0\", + \"eslint\": \"8.56.0\" + } + }""" + + with tempfile.TemporaryDirectory() as tmpdir: + pkg_path = Path(tmpdir) / "package.json" + pkg_path.write_text(content) + + parser = NpmParser() + deps = parser.parse(pkg_path) + + assert len(deps) == 2 + assert all(d.category == "devDependencies" for d in deps) + + def test_parse_optional_dependencies(self): + """Test parsing optionalDependencies.""" + content = """{ + \"optionalDependencies\": { + \"fsevents\": \"2.3.3\" + } + }""" + + with tempfile.TemporaryDirectory() as tmpdir: + pkg_path = Path(tmpdir) / "package.json" + pkg_path.write_text(content) + + parser = NpmParser() + deps = parser.parse(pkg_path) + + assert len(deps) == 1 + assert deps[0].category == "optionalDependencies" + + def test_invalid_json_handled_gracefully(self): + """Test that invalid JSON is handled gracefully.""" + content = "{ invalid json }" + + with tempfile.TemporaryDirectory() as tmpdir: + pkg_path = Path(tmpdir) / "package.json" + pkg_path.write_text(content) + + parser = NpmParser() + deps = parser.parse(pkg_path) + + assert len(deps) == 0 + + +class TestPipParser: + """Tests for pip requirements.txt parser.""" + + def test_parse_requirements_txt(self): + """Test parsing requirements.txt.""" + content = """# This is a comment +requests>=2.31.0 +flask>=2.0.0 +numpy==1.24.0 +""" + + with tempfile.TemporaryDirectory() as tmpdir: + req_path = Path(tmpdir) / "requirements.txt" + req_path.write_text(content) + + parser = PipParser() + deps = parser.parse(req_path) + + assert len(deps) == 3 + assert any(d.name == "requests" for d in deps) + assert any(d.name == "flask" for d in deps) + assert any(d.name == "numpy" for d in deps) + + def test_parse_requirements_with_comments(self): + """Test that comments are ignored.""" + content = """requests==2.31.0 # inline comment +flask>=2.0.0; python_version >= \"3.8\" +""" + + with tempfile.TemporaryDirectory() as tmpdir: + req_path = Path(tmpdir) / "requirements.txt" + req_path.write_text(content) + + parser = PipParser() + deps = parser.parse(req_path) + + assert len(deps) >= 1 + + def test_parse_pyproject_toml(self): + """Test parsing pyproject.toml.""" + content = """ +[project] +dependencies = [ + \"requests>=2.31.0\", + \"flask>=2.0.0\" +] +""" + + with tempfile.TemporaryDirectory() as tmpdir: + toml_path = Path(tmpdir) / "pyproject.toml" + toml_path.write_text(content) + + parser = PipParser() + deps = parser.parse(toml_path) + + assert len(deps) == 2 + + +class TestGoParser: + """Tests for Go go.mod parser.""" + + def test_parse_go_mod(self): + """Test parsing go.mod file.""" + content = """ +module github.com/example/project + +go 1.21 + +require ( + github.com/stretchr/testify v1.8.4 + golang.org/x/crypto v0.17.0 +) +""" + + with tempfile.TemporaryDirectory() as tmpdir: + mod_path = Path(tmpdir) / "go.mod" + mod_path.write_text(content) + + parser = GoParser() + deps = parser.parse(mod_path) + + assert len(deps) == 2 + assert any("testify" in d.name for d in deps) + assert any("crypto" in d.name for d in deps) + + +class TestCargoParser: + """Tests for Rust Cargo.toml parser.""" + + def test_parse_cargo_toml(self): + """Test parsing Cargo.toml file.""" + content = """ +[package] +name = \"my-project\" +version = \"0.1.0\" +edition = \"2021\" + +[dependencies] +serde = \"1.0\" +tokio = \"1.36\" + +[dev-dependencies] +proptest = \"1.4\" +""" + + with tempfile.TemporaryDirectory() as tmpdir: + cargo_path = Path(tmpdir) / "Cargo.toml" + cargo_path.write_text(content) + + parser = CargoParser() + deps = parser.parse(cargo_path) + + assert len(deps) == 3 + assert any(d.name == "serde" for d in deps) + assert any(d.name == "tokio" for d in deps) + assert any(d.name == "proptest" for d in deps) + + +class TestParserRegistry: + """Tests for parser registry.""" + + def test_get_parser_for_package_json(self): + """Test getting correct parser for package.json.""" + registry = create_parser_registry() + + with tempfile.TemporaryDirectory() as tmpdir: + pkg_path = Path(tmpdir) / "package.json" + pkg_path.write_text('{"name": "test"}') + + parser = registry.get_parser(pkg_path) + assert parser is not None + assert parser.package_manager == PackageManager.NPM + + def test_get_parser_for_requirements(self): + """Test getting correct parser for requirements.txt.""" + registry = create_parser_registry() + + with tempfile.TemporaryDirectory() as tmpdir: + req_path = Path(tmpdir) / "requirements.txt" + req_path.write_text("requests==2.31.0") + + parser = registry.get_parser(req_path) + assert parser is not None + assert parser.package_manager == PackageManager.PIP + + def test_no_parser_for_unknown_file(self): + """Test that unknown file types return None.""" + registry = create_parser_registry() + + with tempfile.TemporaryDirectory() as tmpdir: + unknown_path = Path(tmpdir) / "unknown.ext" + unknown_path.write_text("some content") + + parser = registry.get_parser(unknown_path) + assert parser is None