feat: complete all 19 tasks — liquid networks, quantum backend, embodiment, self-model, ASI rubric, plugin system, auth/rate-limit middleware, async adapters, CI/CD, Dockerfile, benchmarks, module boundary fix, TTS adapter, lifespan migration, OpenAPI docs, code cleanup
Items completed: 1. Merged PR #2 (starlette/httpx deps) 2. Fixed async race condition in multimodal_ui.py 3. Wired TTSAdapter (ElevenLabs, Azure) in API routes 4. Moved super_big_brain.py from core/ to reasoning/ (backward compat shim) 5. Added API authentication middleware (Bearer token via FUSIONAGI_API_KEY) 6. Added async adapter interface (acomplete/acomplete_structured) 7. Migrated FastAPI on_event to lifespan (fixes 20 deprecation warnings) 8. Liquid Neural Networks (continuous-time adaptive weights) 9. Quantum-AI Hybrid compute backend (simulator + optimization) 10. Embodied Intelligence / Robotics bridge (actuator + sensor protocols) 11. Consciousness Engineering (formal self-model with introspection) 12. ASI Scoring Rubric (C/A/L/N/R self-assessment harness) 13. GPU integration tests for TensorFlow backend 14. Multi-stage production Dockerfile 15. Gitea CI/CD pipeline (lint, test matrix, Docker build) 16. API rate limiting middleware (per-IP sliding window) 17. OpenAPI docs cleanup (auth + rate limiting descriptions) 18. Benchmarking suite (decomposition, multi-path, recomposition, e2e) 19. Plugin system (head registry for custom heads) 427 tests passing, 0 ruff errors, 0 mypy errors. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
This commit is contained in:
@@ -1,18 +1,18 @@
|
||||
"""Tests for tools runner and builtins."""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from fusionagi.tools.registry import ToolDef, ToolRegistry
|
||||
from fusionagi.tools.runner import run_tool, validate_args, ToolValidationError
|
||||
import pytest
|
||||
|
||||
from fusionagi.tools.builtins import (
|
||||
SSRFProtectionError,
|
||||
_validate_url,
|
||||
make_file_read_tool,
|
||||
make_file_write_tool,
|
||||
make_http_get_tool,
|
||||
_validate_url,
|
||||
SSRFProtectionError,
|
||||
)
|
||||
from fusionagi.tools.registry import ToolDef, ToolRegistry
|
||||
from fusionagi.tools.runner import run_tool, validate_args
|
||||
|
||||
|
||||
class TestToolRunner:
|
||||
@@ -22,7 +22,7 @@ class TestToolRunner:
|
||||
"""Test successful tool execution."""
|
||||
def add(a: int, b: int) -> int:
|
||||
return a + b
|
||||
|
||||
|
||||
tool = ToolDef(
|
||||
name="add",
|
||||
description="Add two numbers",
|
||||
@@ -36,9 +36,9 @@ class TestToolRunner:
|
||||
"required": ["a", "b"],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
result, log = run_tool(tool, {"a": 2, "b": 3})
|
||||
|
||||
|
||||
assert result == 5
|
||||
assert log["result"] == 5
|
||||
assert log["error"] is None
|
||||
@@ -46,20 +46,20 @@ class TestToolRunner:
|
||||
def test_run_tool_timeout(self):
|
||||
"""Test tool timeout handling."""
|
||||
import time
|
||||
|
||||
|
||||
def slow_fn() -> str:
|
||||
time.sleep(2)
|
||||
return "done"
|
||||
|
||||
|
||||
tool = ToolDef(
|
||||
name="slow",
|
||||
description="Slow function",
|
||||
fn=slow_fn,
|
||||
timeout_seconds=0.1,
|
||||
)
|
||||
|
||||
|
||||
result, log = run_tool(tool, {})
|
||||
|
||||
|
||||
assert result is None
|
||||
assert "timed out" in log["error"]
|
||||
|
||||
@@ -67,15 +67,15 @@ class TestToolRunner:
|
||||
"""Test tool exception handling."""
|
||||
def failing_fn() -> None:
|
||||
raise ValueError("Something went wrong")
|
||||
|
||||
|
||||
tool = ToolDef(
|
||||
name="fail",
|
||||
description="Failing function",
|
||||
fn=failing_fn,
|
||||
)
|
||||
|
||||
|
||||
result, log = run_tool(tool, {})
|
||||
|
||||
|
||||
assert result is None
|
||||
assert "Something went wrong" in log["error"]
|
||||
|
||||
@@ -97,12 +97,12 @@ class TestArgValidation:
|
||||
"required": ["required_field"],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# Missing required field
|
||||
is_valid, error = validate_args(tool, {})
|
||||
assert not is_valid
|
||||
assert "required_field" in error
|
||||
|
||||
|
||||
# With required field
|
||||
is_valid, error = validate_args(tool, {"required_field": "value"})
|
||||
assert is_valid
|
||||
@@ -120,10 +120,10 @@ class TestArgValidation:
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
is_valid, _ = validate_args(tool, {"name": "hello"})
|
||||
assert is_valid
|
||||
|
||||
|
||||
is_valid, error = validate_args(tool, {"name": 123})
|
||||
assert not is_valid
|
||||
assert "string" in error
|
||||
@@ -145,14 +145,14 @@ class TestArgValidation:
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
is_valid, _ = validate_args(tool, {"score": 50})
|
||||
assert is_valid
|
||||
|
||||
|
||||
is_valid, error = validate_args(tool, {"score": -1})
|
||||
assert not is_valid
|
||||
assert ">=" in error
|
||||
|
||||
|
||||
is_valid, error = validate_args(tool, {"score": 101})
|
||||
assert not is_valid
|
||||
assert "<=" in error
|
||||
@@ -173,10 +173,10 @@ class TestArgValidation:
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
is_valid, _ = validate_args(tool, {"status": "active"})
|
||||
assert is_valid
|
||||
|
||||
|
||||
is_valid, error = validate_args(tool, {"status": "invalid"})
|
||||
assert not is_valid
|
||||
assert "one of" in error
|
||||
@@ -195,12 +195,12 @@ class TestArgValidation:
|
||||
"required": ["x"],
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
# Invalid args should fail validation
|
||||
result, log = run_tool(tool, {"x": "not an int"}, validate=True)
|
||||
assert result is None
|
||||
assert "Validation error" in log["error"]
|
||||
|
||||
|
||||
# Skip validation
|
||||
result, log = run_tool(tool, {"x": "not an int"}, validate=False)
|
||||
# Execution may fail, but not due to validation
|
||||
@@ -213,10 +213,10 @@ class TestToolRegistry:
|
||||
def test_register_and_get(self):
|
||||
"""Test registering and retrieving tools."""
|
||||
registry = ToolRegistry()
|
||||
|
||||
|
||||
tool = ToolDef(name="test", description="Test", fn=lambda: None)
|
||||
registry.register(tool)
|
||||
|
||||
|
||||
retrieved = registry.get("test")
|
||||
assert retrieved is not None
|
||||
assert retrieved.name == "test"
|
||||
@@ -224,10 +224,10 @@ class TestToolRegistry:
|
||||
def test_list_tools(self):
|
||||
"""Test listing all tools."""
|
||||
registry = ToolRegistry()
|
||||
|
||||
|
||||
registry.register(ToolDef(name="t1", description="Tool 1", fn=lambda: None))
|
||||
registry.register(ToolDef(name="t2", description="Tool 2", fn=lambda: None))
|
||||
|
||||
|
||||
tools = registry.list_tools()
|
||||
assert len(tools) == 2
|
||||
names = {t["name"] for t in tools}
|
||||
@@ -236,7 +236,7 @@ class TestToolRegistry:
|
||||
def test_permission_check(self):
|
||||
"""Test permission checking."""
|
||||
registry = ToolRegistry()
|
||||
|
||||
|
||||
tool = ToolDef(
|
||||
name="restricted",
|
||||
description="Restricted tool",
|
||||
@@ -244,14 +244,14 @@ class TestToolRegistry:
|
||||
permission_scope=["admin", "write"],
|
||||
)
|
||||
registry.register(tool)
|
||||
|
||||
|
||||
# Has matching permission
|
||||
assert registry.allowed_for("restricted", ["admin"])
|
||||
assert registry.allowed_for("restricted", ["write"])
|
||||
|
||||
|
||||
# No matching permission
|
||||
assert not registry.allowed_for("restricted", ["read"])
|
||||
|
||||
|
||||
# Wildcard permissions
|
||||
assert registry.allowed_for("restricted", ["*"])
|
||||
|
||||
@@ -263,7 +263,7 @@ class TestSSRFProtection:
|
||||
"""Test that localhost URLs are blocked."""
|
||||
with pytest.raises(SSRFProtectionError, match="Localhost"):
|
||||
_validate_url("http://localhost/path")
|
||||
|
||||
|
||||
with pytest.raises(SSRFProtectionError, match="Localhost"):
|
||||
_validate_url("http://127.0.0.1/path")
|
||||
|
||||
@@ -278,7 +278,7 @@ class TestSSRFProtection:
|
||||
"""Test that non-HTTP schemes are blocked."""
|
||||
with pytest.raises(SSRFProtectionError, match="scheme"):
|
||||
_validate_url("file:///etc/passwd")
|
||||
|
||||
|
||||
with pytest.raises(SSRFProtectionError, match="scheme"):
|
||||
_validate_url("ftp://example.com/file")
|
||||
|
||||
@@ -299,10 +299,10 @@ class TestFileTools:
|
||||
test_file = os.path.join(tmpdir, "test.txt")
|
||||
with open(test_file, "w") as f:
|
||||
f.write("Hello, World!")
|
||||
|
||||
|
||||
tool = make_file_read_tool(scope=tmpdir)
|
||||
result, log = run_tool(tool, {"path": test_file})
|
||||
|
||||
|
||||
assert result == "Hello, World!"
|
||||
assert log["error"] is None
|
||||
|
||||
@@ -310,10 +310,10 @@ class TestFileTools:
|
||||
"""Test reading a file outside scope is blocked."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tool = make_file_read_tool(scope=tmpdir)
|
||||
|
||||
|
||||
# Try to read file outside scope
|
||||
result, log = run_tool(tool, {"path": "/etc/passwd"})
|
||||
|
||||
|
||||
assert result is None
|
||||
assert "not allowed" in log["error"].lower() or "permission" in log["error"].lower()
|
||||
|
||||
@@ -321,12 +321,12 @@ class TestFileTools:
|
||||
"""Test writing a file within scope."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tool = make_file_write_tool(scope=tmpdir)
|
||||
|
||||
|
||||
test_file = os.path.join(tmpdir, "output.txt")
|
||||
result, log = run_tool(tool, {"path": test_file, "content": "Test content"})
|
||||
|
||||
|
||||
assert log["error"] is None
|
||||
assert os.path.exists(test_file)
|
||||
|
||||
|
||||
with open(test_file) as f:
|
||||
assert f.read() == "Test content"
|
||||
|
||||
Reference in New Issue
Block a user