From 508f0363b575433aae9d01b7c669e337e2ba4c91 Mon Sep 17 00:00:00 2001 From: Alexander Whitestone Date: Wed, 15 Apr 2026 03:32:58 +0000 Subject: [PATCH] test: license checker unit tests (#110) --- tests/test_license_checker.py | 186 ++++++++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 tests/test_license_checker.py diff --git a/tests/test_license_checker.py b/tests/test_license_checker.py new file mode 100644 index 0000000..c9b7105 --- /dev/null +++ b/tests/test_license_checker.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python3 +"""Tests for license_checker.py — Pipeline 5.4""" + +import json +import os +import sys +import tempfile +import unittest + +# Add scripts dir to path +sys.path.insert(0, os.path.dirname(__file__)) + +from license_checker import ( + normalize_license, + check_compatibility, + parse_requirements_txt, + parse_package_json, + parse_pyproject_toml, + parse_go_mod, + detect_project_license, + scan_dep_files, + generate_report, + format_text, + Severity, + DepLicense, +) + + +class TestNormalizeLicense(unittest.TestCase): + def test_mit_aliases(self): + for alias in ["mit", "MIT License", "The MIT License", "MIT license"]: + self.assertEqual(normalize_license(alias), "MIT") + + def test_apache_aliases(self): + for alias in ["Apache 2.0", "Apache-2.0", "apache software license"]: + self.assertEqual(normalize_license(alias), "Apache-2.0") + + def test_gpl_aliases(self): + self.assertEqual(normalize_license("GPL-3.0"), "GPL-3.0") + self.assertEqual(normalize_license("gplv3"), "GPL-3.0") + + def test_unknown(self): + self.assertEqual(normalize_license(""), "UNKNOWN") + self.assertEqual(normalize_license("UNKNOWN"), "UNKNOWN") + + def test_already_spdx(self): + self.assertEqual(normalize_license("BSD-3-Clause"), "BSD-3-Clause") + + +class TestCheckCompatibility(unittest.TestCase): + def test_permissive_ok(self): + sev, msg = check_compatibility("MIT", "MIT") + self.assertEqual(sev, Severity.OK) + + def test_gpl_in_mit_error(self): + sev, msg = check_compatibility("GPL-3.0", "MIT") + self.assertEqual(sev, Severity.ERROR) + + def test_unknown_warning(self): + sev, msg = check_compatibility("UNKNOWN", "MIT") + self.assertEqual(sev, Severity.WARNING) + + def test_apache_in_mit_ok(self): + sev, msg = check_compatibility("Apache-2.0", "MIT") + self.assertEqual(sev, Severity.OK) + + def test_lgpl_in_mit_error(self): + sev, msg = check_compatibility("LGPL-3.0", "MIT") + self.assertEqual(sev, Severity.ERROR) + + +class TestParseRequirements(unittest.TestCase): + def test_basic(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f: + f.write("requests>=2.28.0\nflask==2.3.0\n# comment\npytest\n") + f.flush() + deps = parse_requirements_txt(f.name) + os.unlink(f.name) + names = [d.name for d in deps] + self.assertIn("requests", names) + self.assertIn("flask", names) + self.assertIn("pytest", names) + self.assertEqual(len(deps), 3) + + def test_skip_flags(self): + with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f: + f.write("-r other.txt\n--index-url https://pypi.org\nreal-dep\n") + f.flush() + deps = parse_requirements_txt(f.name) + os.unlink(f.name) + self.assertEqual(len(deps), 1) + self.assertEqual(deps[0].name, "real-dep") + + +class TestParsePackageJson(unittest.TestCase): + def test_basic(self): + data = { + "dependencies": {"express": "^4.18.0", "lodash": "^4.17.21"}, + "devDependencies": {"jest": "^29.0.0"}, + } + with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as f: + json.dump(data, f) + f.flush() + deps = parse_package_json(f.name) + os.unlink(f.name) + names = [d.name for d in deps] + self.assertIn("express", names) + self.assertIn("jest", names) + self.assertEqual(len(deps), 3) + + +class TestParseGoMod(unittest.TestCase): + def test_basic(self): + content = """module example.com/mymod + +go 1.21 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/stretchr/testify v1.8.4 +) +""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".mod", delete=False) as f: + f.write(content) + f.flush() + deps = parse_go_mod(f.name) + os.unlink(f.name) + self.assertEqual(len(deps), 2) + self.assertEqual(deps[0].name, "github.com/gin-gonic/gin") + + +class TestDetectProjectLicense(unittest.TestCase): + def test_mit_file(self): + with tempfile.TemporaryDirectory() as d: + with open(os.path.join(d, "LICENSE"), "w") as f: + f.write("MIT License\n\nCopyright (c) 2024...\n") + self.assertEqual(detect_project_license(d), "MIT") + + def test_apache_file(self): + with tempfile.TemporaryDirectory() as d: + with open(os.path.join(d, "LICENSE"), "w") as f: + f.write("Apache License Version 2.0...") + self.assertEqual(detect_project_license(d), "Apache-2.0") + + def test_no_license(self): + with tempfile.TemporaryDirectory() as d: + self.assertEqual(detect_project_license(d), "UNKNOWN") + + +class TestScanDeps(unittest.TestCase): + def test_multi_ecosystem(self): + with tempfile.TemporaryDirectory() as d: + with open(os.path.join(d, "requirements.txt"), "w") as f: + f.write("flask\nrequests\n") + with open(os.path.join(d, "package.json"), "w") as f: + json.dump({"dependencies": {"express": "^4.0.0"}}, f) + deps = scan_dep_files(d) + names = [d.name for d in deps] + self.assertIn("flask", names) + self.assertIn("express", names) + + +class TestGenerateReport(unittest.TestCase): + def test_basic(self): + deps = [ + DepLicense(name="flask", license="BSD-3-Clause", source="requirements.txt"), + DepLicense(name="gpl-pkg", license="GPL-3.0", source="requirements.txt"), + DepLicense(name="unknown-pkg", license="UNKNOWN", source="requirements.txt"), + ] + report = generate_report(deps, "MIT") + self.assertEqual(report.summary["ok"], 1) + self.assertEqual(report.summary["error"], 1) + self.assertEqual(report.summary["warning"], 1) + self.assertEqual(len(report.errors), 1) + self.assertIn("gpl-pkg", report.errors[0]) + + def test_format_text(self): + deps = [DepLicense(name="flask", license="BSD-3-Clause", source="requirements.txt")] + report = generate_report(deps, "MIT") + text = format_text(report) + self.assertIn("LICENSE COMPATIBILITY REPORT", text) + self.assertIn("flask", text) + + +if __name__ == "__main__": + unittest.main()