mirror of https://github.com/razor-ai/soup.git
329 lines
10 KiB
Python
329 lines
10 KiB
Python
"""v0.64.0 Part F — License advisor tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import dataclasses
|
|
|
|
import pytest
|
|
from typer.testing import CliRunner
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
def test_module_imports():
|
|
from soup_cli.utils import license_advisor
|
|
|
|
assert hasattr(license_advisor, "DEPLOY_TARGETS")
|
|
assert hasattr(license_advisor, "validate_deploy_target")
|
|
assert hasattr(license_advisor, "LicenseRecommendation")
|
|
assert hasattr(license_advisor, "advise_license_for_target")
|
|
assert hasattr(license_advisor, "flag_downstream_risk")
|
|
assert hasattr(license_advisor, "DownstreamRisk")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DEPLOY_TARGETS
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_deploy_targets_exact():
|
|
from soup_cli.utils.license_advisor import DEPLOY_TARGETS
|
|
|
|
assert "b2c" in DEPLOY_TARGETS
|
|
assert "defense" in DEPLOY_TARGETS
|
|
assert "embedded" in DEPLOY_TARGETS
|
|
|
|
|
|
def test_deploy_targets_is_frozenset():
|
|
from soup_cli.utils.license_advisor import DEPLOY_TARGETS
|
|
|
|
assert isinstance(DEPLOY_TARGETS, frozenset)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# validate_deploy_target
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@pytest.mark.parametrize("v", ["b2c", "defense", "embedded"])
|
|
def test_validate_deploy_target_happy(v):
|
|
from soup_cli.utils.license_advisor import validate_deploy_target
|
|
|
|
assert validate_deploy_target(v) == v
|
|
|
|
|
|
def test_validate_deploy_target_case_insensitive():
|
|
from soup_cli.utils.license_advisor import validate_deploy_target
|
|
|
|
assert validate_deploy_target("B2C") == "b2c"
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"bad",
|
|
[True, False, None, "", "consumer", "x" * 33],
|
|
)
|
|
def test_validate_deploy_target_rejects(bad):
|
|
from soup_cli.utils.license_advisor import validate_deploy_target
|
|
|
|
with pytest.raises((TypeError, ValueError)):
|
|
validate_deploy_target(bad)
|
|
|
|
|
|
def test_validate_deploy_target_null_byte():
|
|
from soup_cli.utils.license_advisor import validate_deploy_target
|
|
|
|
with pytest.raises(ValueError, match="null"):
|
|
validate_deploy_target("b\x002c")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# LicenseRecommendation
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_license_recommendation_frozen():
|
|
from soup_cli.utils.license_advisor import LicenseRecommendation
|
|
|
|
r = LicenseRecommendation(
|
|
target="b2c",
|
|
recommended_licenses=("apache-2.0", "mit"),
|
|
forbidden_licenses=("cc-by-nc-4.0",),
|
|
reason="permissive only",
|
|
)
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
r.target = "other" # type: ignore[misc]
|
|
|
|
|
|
def test_license_recommendation_tuples():
|
|
from soup_cli.utils.license_advisor import LicenseRecommendation
|
|
|
|
# Lists rejected (frozen=True doesn't make lists immutable)
|
|
with pytest.raises(TypeError, match="tuple"):
|
|
LicenseRecommendation(
|
|
target="b2c",
|
|
recommended_licenses=["apache-2.0"], # type: ignore[arg-type]
|
|
forbidden_licenses=(),
|
|
reason="x",
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# advise_license_for_target
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_advise_license_b2c():
|
|
from soup_cli.utils.license_advisor import advise_license_for_target
|
|
|
|
rec = advise_license_for_target("b2c")
|
|
# B2C must include broadly-permissive options
|
|
assert "apache-2.0" in rec.recommended_licenses
|
|
assert "mit" in rec.recommended_licenses
|
|
# Non-commercial licenses must be forbidden for B2C
|
|
assert "cc-by-nc-4.0" in rec.forbidden_licenses
|
|
|
|
|
|
def test_advise_license_defense():
|
|
from soup_cli.utils.license_advisor import advise_license_for_target
|
|
|
|
rec = advise_license_for_target("defense")
|
|
# Defense forbids restricted-use community licenses
|
|
assert "llama-3.1" in rec.forbidden_licenses or "llama-2" in rec.forbidden_licenses
|
|
|
|
|
|
def test_advise_license_embedded():
|
|
from soup_cli.utils.license_advisor import advise_license_for_target
|
|
|
|
rec = advise_license_for_target("embedded")
|
|
# Embedded systems should avoid strong copyleft
|
|
assert "gpl-3.0" in rec.forbidden_licenses
|
|
|
|
|
|
def test_advise_license_rejects_unknown_target():
|
|
from soup_cli.utils.license_advisor import advise_license_for_target
|
|
|
|
with pytest.raises(ValueError):
|
|
advise_license_for_target("bogus")
|
|
|
|
|
|
def test_advise_license_rejects_bool():
|
|
from soup_cli.utils.license_advisor import advise_license_for_target
|
|
|
|
with pytest.raises(TypeError):
|
|
advise_license_for_target(True) # type: ignore[arg-type]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DownstreamRisk
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_downstream_risk_frozen():
|
|
from soup_cli.utils.license_advisor import DownstreamRisk
|
|
|
|
r = DownstreamRisk(ok=True, severity="ok", reason="permissive license")
|
|
with pytest.raises(dataclasses.FrozenInstanceError):
|
|
r.ok = False # type: ignore[misc]
|
|
|
|
|
|
def test_downstream_risk_severity_allowlist():
|
|
from soup_cli.utils.license_advisor import DownstreamRisk
|
|
|
|
with pytest.raises(ValueError, match="severity"):
|
|
DownstreamRisk(ok=False, severity="weird", reason="x")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# flag_downstream_risk
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_flag_downstream_risk_apache_b2c_ok():
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
r = flag_downstream_risk(license_id="apache-2.0", target="b2c", monthly_active_users=10_000)
|
|
assert r.ok is True
|
|
assert r.severity == "ok"
|
|
|
|
|
|
def test_flag_downstream_risk_llama_community_high_mau():
|
|
"""Llama community license + commercial use > 700M MAU is the canonical risk."""
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
r = flag_downstream_risk(
|
|
license_id="llama-3", target="b2c", monthly_active_users=800_000_000
|
|
)
|
|
assert r.ok is False
|
|
assert r.severity in ("warn", "block")
|
|
|
|
|
|
def test_flag_downstream_risk_llama_community_low_mau_warns():
|
|
"""Llama community on small B2C: warning, not block."""
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
r = flag_downstream_risk(
|
|
license_id="llama-3", target="b2c", monthly_active_users=10_000
|
|
)
|
|
# Either OK with note or a warn — not a hard block
|
|
assert r.severity in ("ok", "warn")
|
|
|
|
|
|
def test_flag_downstream_risk_nc_b2c_blocked():
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
r = flag_downstream_risk(
|
|
license_id="cc-by-nc-4.0", target="b2c", monthly_active_users=100
|
|
)
|
|
assert r.ok is False
|
|
|
|
|
|
def test_flag_downstream_risk_rejects_negative_mau():
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
with pytest.raises(ValueError, match="monthly_active_users"):
|
|
flag_downstream_risk(
|
|
license_id="apache-2.0", target="b2c", monthly_active_users=-1
|
|
)
|
|
|
|
|
|
def test_flag_downstream_risk_rejects_bool_mau():
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
with pytest.raises(TypeError, match="bool"):
|
|
flag_downstream_risk(
|
|
license_id="apache-2.0", target="b2c",
|
|
monthly_active_users=True, # type: ignore[arg-type]
|
|
)
|
|
|
|
|
|
def test_flag_downstream_risk_unknown_license_warns():
|
|
from soup_cli.utils.license_advisor import flag_downstream_risk
|
|
|
|
r = flag_downstream_risk(
|
|
license_id="unknown-soup-test-license", target="b2c", monthly_active_users=1
|
|
)
|
|
# Unknown license should not block silently — surface as a warn
|
|
assert r.severity in ("warn", "block")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CLI smoke
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_cli_license_advisor_help():
|
|
from soup_cli.cli import app
|
|
|
|
result = runner.invoke(app, ["license-advisor", "--help"])
|
|
assert result.exit_code == 0, (result.output, repr(result.exception))
|
|
|
|
|
|
def test_cli_license_advisor_b2c():
|
|
from soup_cli.cli import app
|
|
|
|
result = runner.invoke(app, ["license-advisor", "--target", "b2c"])
|
|
assert result.exit_code == 0, (result.output, repr(result.exception))
|
|
|
|
|
|
def test_cli_license_advisor_unknown_target():
|
|
from soup_cli.cli import app
|
|
|
|
result = runner.invoke(app, ["license-advisor", "--target", "bogus"])
|
|
assert result.exit_code != 0
|
|
|
|
|
|
def test_cli_license_advisor_check_license():
|
|
"""--license is the per-license-id risk-check mode."""
|
|
from soup_cli.cli import app
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"license-advisor",
|
|
"--target",
|
|
"b2c",
|
|
"--license",
|
|
"apache-2.0",
|
|
"--monthly-active-users",
|
|
"10000",
|
|
],
|
|
)
|
|
assert result.exit_code == 0, (result.output, repr(result.exception))
|
|
|
|
|
|
def test_cli_license_advisor_check_llama_block():
|
|
from soup_cli.cli import app
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"license-advisor",
|
|
"--target",
|
|
"b2c",
|
|
"--license",
|
|
"llama-3",
|
|
"--monthly-active-users",
|
|
"800000000",
|
|
],
|
|
)
|
|
# Either exits non-zero on block, OR prints a block message
|
|
assert result.exit_code != 0 or "block" in result.output.lower()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Source-wiring regression
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_no_heavy_top_level_imports():
|
|
from pathlib import Path
|
|
|
|
src = (
|
|
Path(__file__).resolve().parent.parent
|
|
/ "src" / "soup_cli" / "utils" / "license_advisor.py"
|
|
)
|
|
text = src.read_text(encoding="utf-8")
|
|
import re
|
|
for bad in ["^import torch", "^from torch", "^import transformers", "^from transformers"]:
|
|
assert not re.search(bad, text, re.MULTILINE)
|