diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 000000000..0179967de --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,49 @@ +name: Forge CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: forge-ci-${{ gitea.ref }} + cancel-in-progress: true + +jobs: + smoke-and-build: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Python 3.11 + run: uv python install 3.11 + + - name: Install package + run: | + uv venv .venv --python 3.11 + source .venv/bin/activate + uv pip install -e ".[all,dev]" + + - name: Smoke tests + run: | + source .venv/bin/activate + python scripts/smoke_test.py + env: + OPENROUTER_API_KEY: "" + OPENAI_API_KEY: "" + NOUS_API_KEY: "" + + - name: Green-path E2E + run: | + source .venv/bin/activate + python -m pytest tests/test_green_path_e2e.py -q --tb=short + env: + OPENROUTER_API_KEY: "" + OPENAI_API_KEY: "" + NOUS_API_KEY: "" diff --git a/scripts/smoke_test.py b/scripts/smoke_test.py new file mode 100755 index 000000000..231c0081c --- /dev/null +++ b/scripts/smoke_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Forge smoke tests — fast checks that core imports resolve and entrypoints load. + +Total runtime target: < 30 seconds. +""" + +from __future__ import annotations + +import importlib +import subprocess +import sys + +CORE_MODULES = [ + "hermes_cli.config", + "hermes_state", + "model_tools", + "toolsets", + "utils", +] + +CLI_ENTRYPOINTS = [ + ["python", "cli.py", "--help"], +] + + +def test_imports() -> None: + for mod in CORE_MODULES: + try: + importlib.import_module(mod) + except Exception as exc: + print(f"FAIL: import {mod} -> {exc}") + sys.exit(1) + print(f"OK: {len(CORE_MODULES)} core imports") + + +def test_cli_help() -> None: + for cmd in CLI_ENTRYPOINTS: + result = subprocess.run(cmd, capture_output=True, timeout=30) + if result.returncode != 0: + stderr = result.stderr.decode()[:200] + print(f"FAIL: {' '.join(cmd)} -> {stderr}") + sys.exit(1) + print(f"OK: {len(CLI_ENTRYPOINTS)} CLI entrypoints") + + +def main() -> int: + test_imports() + test_cli_help() + print("Smoke tests passed.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_green_path_e2e.py b/tests/test_green_path_e2e.py new file mode 100644 index 000000000..95898a649 --- /dev/null +++ b/tests/test_green_path_e2e.py @@ -0,0 +1,18 @@ +"""Bare green-path E2E — one happy-path tool call cycle. + +Exercises the terminal tool directly and verifies the response structure. +No API keys required. Runtime target: < 10 seconds. +""" + +import json + +from tools.terminal_tool import terminal_tool + + +def test_terminal_echo_green_path() -> None: + """terminal('echo hello') -> verify response contains 'hello' and exit_code 0.""" + result = terminal_tool(command="echo hello", timeout=10) + data = json.loads(result) + + assert data["exit_code"] == 0, f"Expected exit_code 0, got {data['exit_code']}" + assert "hello" in data["output"], f"Expected 'hello' in output, got: {data['output']}"