mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-28 13:01:38 +00:00
* merge branch develop with main * bump desktop version * fix the merge * sync Cargo.lock to desktop 2.0.0 * fix cli conftest.py --------- Co-authored-by: deeleeramone <>
214 lines
7.3 KiB
Python
214 lines
7.3 KiB
Python
"""Tests for the project template."""
|
|
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from cookiecutter.exceptions import FailedHookException
|
|
from cookiecutter.main import cookiecutter
|
|
|
|
from openbb_cookiecutter import get_template_path
|
|
|
|
if sys.version_info >= (3, 11):
|
|
import tomllib
|
|
else:
|
|
import tomli as tomllib
|
|
|
|
PACKAGE = "extension_template"
|
|
|
|
|
|
def generate(tmp_path: Path, extension_types: str, **context: str) -> Path:
|
|
return Path(
|
|
cookiecutter(
|
|
str(get_template_path()),
|
|
output_dir=str(tmp_path),
|
|
no_input=True,
|
|
extra_context={"extension_types": extension_types, **context},
|
|
)
|
|
)
|
|
|
|
|
|
def files(project: Path) -> set[str]:
|
|
return {
|
|
p.relative_to(project).as_posix() for p in project.rglob("*") if p.is_file()
|
|
}
|
|
|
|
|
|
def entry_points(project: Path) -> dict:
|
|
return tomllib.loads((project / "pyproject.toml").read_text(encoding="utf-8"))[
|
|
"project"
|
|
].get("entry-points", {})
|
|
|
|
|
|
class TestGeneratedFiles:
|
|
"""Each extension type generates only its own modules and tests."""
|
|
|
|
@pytest.mark.parametrize(
|
|
("extension_types", "expected"),
|
|
[
|
|
(
|
|
"router",
|
|
{
|
|
f"{PACKAGE}/routers/template.py",
|
|
"tests/test_router.py",
|
|
},
|
|
),
|
|
(
|
|
"provider",
|
|
{
|
|
f"{PACKAGE}/providers/template/models/example.py",
|
|
f"{PACKAGE}/providers/template/models/equity_historical.py",
|
|
"tests/test_provider.py",
|
|
},
|
|
),
|
|
(
|
|
"obbject",
|
|
{
|
|
f"{PACKAGE}/obbject/template_ext/__init__.py",
|
|
"tests/test_obbject.py",
|
|
},
|
|
),
|
|
(
|
|
"on_command_output",
|
|
{
|
|
f"{PACKAGE}/obbject/template_ext/on_command_output.py",
|
|
"tests/test_on_command_output.py",
|
|
},
|
|
),
|
|
(
|
|
"charting",
|
|
{
|
|
f"{PACKAGE}/routers/template_views.py",
|
|
"tests/test_views.py",
|
|
},
|
|
),
|
|
],
|
|
)
|
|
def test_single_type(self, tmp_path, extension_types, expected):
|
|
generated = files(generate(tmp_path, extension_types))
|
|
optional = {
|
|
f"{PACKAGE}/routers/template.py",
|
|
f"{PACKAGE}/routers/template_views.py",
|
|
f"{PACKAGE}/providers/template/models/example.py",
|
|
f"{PACKAGE}/obbject/template_ext/on_command_output.py",
|
|
"tests/test_router.py",
|
|
"tests/test_views.py",
|
|
"tests/test_provider.py",
|
|
"tests/test_obbject.py",
|
|
"tests/test_on_command_output.py",
|
|
}
|
|
assert expected <= generated
|
|
assert not (optional - expected) & generated
|
|
|
|
def test_all_types(self, tmp_path):
|
|
generated = files(generate(tmp_path, "all"))
|
|
assert {
|
|
"pyproject.toml",
|
|
"README.md",
|
|
".gitignore",
|
|
f"{PACKAGE}/routers/template.py",
|
|
f"{PACKAGE}/routers/template_views.py",
|
|
f"{PACKAGE}/providers/template/__init__.py",
|
|
f"{PACKAGE}/obbject/template_ext/__init__.py",
|
|
f"{PACKAGE}/obbject/template_ext/on_command_output.py",
|
|
"tests/test_router.py",
|
|
"tests/test_views.py",
|
|
"tests/test_provider.py",
|
|
"tests/test_obbject.py",
|
|
"tests/test_on_command_output.py",
|
|
} <= generated
|
|
|
|
def test_local_artifacts_are_not_copied(self, tmp_path):
|
|
generated = files(generate(tmp_path, "all"))
|
|
artifacts = (".ruff_cache", ".ipynb_checkpoints", "__pycache__", ".DS_Store")
|
|
assert not [f for f in generated if any(a in f for a in artifacts)]
|
|
|
|
|
|
class TestPyproject:
|
|
"""The generated pyproject declares V5 packaging and entry points."""
|
|
|
|
def test_build_and_dependencies(self, tmp_path):
|
|
pyproject = tomllib.loads(
|
|
(generate(tmp_path, "all") / "pyproject.toml").read_text(encoding="utf-8")
|
|
)
|
|
assert pyproject["build-system"]["build-backend"] == "hatchling.build"
|
|
assert pyproject["project"]["dependencies"] == ["openbb-core[pandas]>=2.0.0"]
|
|
assert pyproject["project"]["optional-dependencies"] == {
|
|
"charting": ["openbb-charting>=3.0.0"]
|
|
}
|
|
assert "openbb-devtools>=2.0.1" in pyproject["dependency-groups"]["dev"]
|
|
|
|
def test_entry_points_for_all_types(self, tmp_path):
|
|
assert entry_points(generate(tmp_path, "all")) == {
|
|
"openbb_core_extension": {
|
|
"template": f"{PACKAGE}.routers.template:router",
|
|
},
|
|
"openbb_charting_extension": {
|
|
"template": f"{PACKAGE}.routers.template_views:TemplateViews",
|
|
},
|
|
"openbb_provider_extension": {
|
|
"template": f"{PACKAGE}.providers.template:template_provider",
|
|
},
|
|
"openbb_obbject_extension": {
|
|
"to_string": f"{PACKAGE}.obbject.template_ext:ext",
|
|
"template_ext": f"{PACKAGE}.obbject.template_ext:class_ext",
|
|
"nonblocking_plugin": (
|
|
f"{PACKAGE}.obbject.template_ext.on_command_output:nonblocking_plugin"
|
|
),
|
|
},
|
|
}
|
|
|
|
def test_entry_points_for_router_only(self, tmp_path):
|
|
assert entry_points(generate(tmp_path, "router")) == {
|
|
"openbb_core_extension": {
|
|
"template": f"{PACKAGE}.routers.template:router",
|
|
},
|
|
}
|
|
|
|
|
|
class TestHooks:
|
|
"""The post-generation hook validates names."""
|
|
|
|
def test_rejects_invalid_package_name(self, tmp_path):
|
|
with pytest.raises(FailedHookException):
|
|
generate(tmp_path, "router", package_name="1-bad")
|
|
|
|
def test_rejects_shared_provider_and_obbject_name(self, tmp_path):
|
|
with pytest.raises(FailedHookException):
|
|
generate(tmp_path, "all", obbject_name="template")
|
|
|
|
|
|
@pytest.mark.skipif(shutil.which("ruff") is None, reason="ruff is not installed")
|
|
class TestGeneratedCodeQuality:
|
|
"""Generated projects pass their own lint and type checks."""
|
|
|
|
@pytest.mark.parametrize(
|
|
"extension_types",
|
|
["router", "provider", "obbject", "on_command_output", "charting", "all"],
|
|
)
|
|
def test_ruff(self, tmp_path, extension_types):
|
|
project = generate(tmp_path, extension_types)
|
|
for command in (["format", "--check"], ["check", "--no-fix"]):
|
|
result = subprocess.run(
|
|
["ruff", *command, "--no-cache", "."],
|
|
cwd=project,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
assert result.returncode == 0, result.stdout + result.stderr
|
|
|
|
@pytest.mark.skipif(shutil.which("ty") is None, reason="ty is not installed")
|
|
def test_ty(self, tmp_path):
|
|
project = generate(tmp_path, "all")
|
|
result = subprocess.run(
|
|
["ty", "check", "--python", sys.executable, PACKAGE],
|
|
cwd=project,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
assert result.returncode == 0, result.stdout + result.stderr
|