diff --git a/tests/unit/test_parsers.py b/tests/unit/test_parsers.py new file mode 100644 index 0000000..a0c93fc --- /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