Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/ci_python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,9 @@ jobs:
"$py" "$s"
echo "::endgroup::"
done

- name: Run pytest
run: |
set -euo pipefail
uv sync --group test --no-group dev
uv run pytest
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ tests/fixtures/*/artifacts/

.DS_Store

# vscode settings
.vscode/

# UV Lock
# TBD if we want to track this in git
uv.lock
Expand Down
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ dependencies = []

[project.optional-dependencies]
harbor = ["harbor>=0.13.2; python_version >= '3.12'"]
hermes = ["hermes-agent>=0.17.0; python_version >= '3.11' and python_version < '3.14'"]

[dependency-groups]
dev = [
Expand All @@ -34,3 +35,7 @@ manifest-path = "crates/fabric-python/Cargo.toml"
python-source = "python/src"
module-name = "nemo_fabric._native"
features = ["pyo3/extension-module"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "session"
51 changes: 51 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import os
import shutil
from pathlib import Path

import pytest

CUR_DIR = Path(__file__).parent.resolve()

@pytest.fixture(name="restore_environ", autouse=True)
def restore_environ_fixture():
"""
Fixture to restore the environment variables after a test.
Since many of the adapters rely on environment variables, this fixture ensures that any changes made to
environment variables during a test are reverted back to their original state after the test completes.
"""
orig_vars = os.environ.copy()
yield os.environ

for key, value in orig_vars.items():
os.environ[key] = value

# Delete any new environment variables
# Iterating over a copy of the keys as we will potentially be deleting keys in the loop
for key in list(os.environ.keys()):
if key not in orig_vars:
del os.environ[key]

@pytest.fixture(name="hermes_cli_agent_dir_src", scope="session")
def hermes_cli_agent_dir_fixture() -> Path:
agent_dir = CUR_DIR / "fixtures" / "hermes-cli-agent"
assert agent_dir.exists(), f"Missing fake Hermes CLI agent directory: {agent_dir}"
return agent_dir

@pytest.fixture(name="hermes_agent_dir")
def hermes_agent_dir_fixture(hermes_cli_agent_dir_src: Path, tmp_path: Path) -> Path:
agent_dir = tmp_path / "hermes-cli-agent"
shutil.copytree(hermes_cli_agent_dir_src, agent_dir)
assert agent_dir.exists(), f"Missing fake Hermes CLI agent directory: {agent_dir}"
return agent_dir.resolve()

@pytest.fixture(name="hermes_cli_profile", scope="session")
def hermes_cli_profile_fixture() -> str:
return "env_local"


@pytest.fixture(name="hermes_command")
def hermes_command_fixture(hermes_agent_dir: Path) -> Path:
hermes_command = hermes_agent_dir / "bin" / "fake-hermes.py"
assert hermes_command.exists(
), f"Missing fake Hermes CLI: {hermes_command}"
return hermes_command.resolve()
32 changes: 32 additions & 0 deletions tests/test_hermes_cli_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from pathlib import Path

from nemo_fabric import FabricClient


async def test_hermes_cli_fields(hermes_command: Path, hermes_agent_dir: Path, hermes_cli_profile: str):
# Ensure the hermes_cli adapter returns expected fields
async with FabricClient() as client:
result = await client.run(hermes_agent_dir,
profile=hermes_cli_profile,
input_text="who are you?")

assert result["status"] == "succeeded"
assert result["adapter_kind"] == "process"
assert result["metadata"]["adapter_runner"] == "process"

output = result["output"]
assert output["adapter"] == "cli"
assert output["command"][0] == hermes_command.as_posix()
assert output["harness"] == "hermes"
assert output["mode"] == "hermes_cli_oneshot"
assert output["model"] == "test-model"

for dir_field in ('cwd', 'fabric_home', 'fabric_invocation', 'hermes_config_path', 'hermes_home'):
# these should all be under the agent dir
dir_path = Path(output[dir_field]).resolve()
assert dir_path.exists(), f"Missing path for field {dir_field}: {dir_path}"
assert dir_path.is_relative_to(hermes_agent_dir), f"Field {dir_field} is not under agent dir: {dir_path}"

for field in ('base_url', 'enabled_toolsets', 'error', 'response'):
# Ensure these fields are present in the output, even if they are None
assert field in output, f"Missing field in output: {field}"