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
Empty file addedAgents/Shared/__init__.py
Empty file.
Empty file addedAgents/__init__.py
Empty file.
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
[tool.pytest.ini_options]
testpaths = ["tests"]
asyncio_mode = "auto"
timeout = 30
pythonpath = ["."]
addopts = "--tb=short -q --cov=Agents --cov-report=term-missing"
5 changes: 5 additions & 0 deletions requirements-test.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
pytest>=8.0
pytest-asyncio>=0.23
pytest-mock>=3.12
pytest-cov>=5.0
freezegun>=1.3
Empty file addedtests/__init__.py
Empty file.
321 changes: 321 additions & 0 deletions tests/conftest.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,321 @@
"""Fixtures partagees pour les tests LandGraph."""
import json
import os
import sys
import types
import importlib
import pytest

# ── Gerer le mapping Agents/ -> agents (Windows case-insensitive) ──
# Le dossier s'appelle Agents/ mais le code importe agents.shared.*
# On cree des aliases dans sys.modules pour que les deux formes marchent.
_repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

def _setup_agents_alias():
"""Pre-importe Agents/ et cree des aliases agents.* dans sys.modules."""
if "agents" in sys.modules:
return

if _repo_root not in sys.path:
sys.path.insert(0, _repo_root)

# Importer les packages principaux
import Agents
import Agents.Shared

sys.modules["agents"] = sys.modules["Agents"]
sys.modules["agents.shared"] = sys.modules["Agents.Shared"]

# Auto-decouvrir et aliaser tous les sous-modules de Agents/Shared/
shared_dir = os.path.join(_repo_root, "Agents", "Shared")
for fname in os.listdir(shared_dir):
if fname.endswith(".py") and fname != "__init__.py":
mod_name = fname[:-3]
real_key = f"Agents.Shared.{mod_name}"
alias_key = f"agents.shared.{mod_name}"
if alias_key not in sys.modules:
try:
importlib.import_module(real_key)
sys.modules[alias_key] = sys.modules[real_key]
except Exception:
pass # Skip modules with missing deps

# Aliaser les modules de premier niveau (gateway, orchestrator, discord_listener)
agents_dir = os.path.join(_repo_root, "Agents")
for fname in os.listdir(agents_dir):
if fname.endswith(".py") and fname != "__init__.py":
mod_name = fname[:-3]
real_key = f"Agents.{mod_name}"
alias_key = f"agents.{mod_name}"
if alias_key not in sys.modules:
try:
importlib.import_module(real_key)
sys.modules[alias_key] = sys.modules[real_key]
except Exception:
pass

_setup_agents_alias()


# ── Fixture : workflow JSON minimal ──────────────

SAMPLE_WORKFLOW = {
"phases": {
"discovery": {
"name": "Discovery",
"order": 1,
"agents": {
"requirements_analyst": {
"role": "Analyste",
"required": True,
"parallel_group": "A",
},
"legal_advisor": {
"role": "Juriste",
"required": False,
"parallel_group": "A",
},
},
"deliverables": {
"prd": {"agent": "requirements_analyst", "required": True},
"legal_audit": {"agent": "legal_advisor", "required": False},
},
"exit_conditions": {"human_gate": True, "no_critical_alerts": True},
},
"design": {
"name": "Design",
"order": 2,
"agents": {
"ux_designer": {
"role": "UX Designer",
"required": True,
"parallel_group": "A",
},
"architect": {
"role": "Architecte",
"required": True,
"parallel_group": "A",
"depends_on": [],
},
},
"deliverables": {
"wireframes": {"agent": "ux_designer", "required": True},
"adr": {"agent": "architect", "required": True},
},
"exit_conditions": {"human_gate": False},
},
"build": {
"name": "Build",
"order": 3,
"agents": {
"lead_dev": {
"role": "Lead Dev",
"required": True,
"parallel_group": "A",
},
"dev_frontend_web": {
"role": "Dev Frontend",
"required": True,
"parallel_group": "B",
"depends_on": ["lead_dev"],
"delegated_by": "lead_dev",
},
"dev_backend_api": {
"role": "Dev Backend",
"required": True,
"parallel_group": "B",
"depends_on": ["lead_dev"],
"delegated_by": "lead_dev",
},
"qa_engineer": {
"role": "QA Engineer",
"required": True,
"parallel_group": "C",
"depends_on": ["dev_frontend_web", "dev_backend_api"],
},
},
"deliverables": {},
"exit_conditions": {},
},
},
"transitions": [
{"from": "discovery", "to": "design"},
{"from": "design", "to": "build"},
{"from": "build", "to": "ship"},
],
"rules": {"max_agents_parallel": 3},
}


SAMPLE_TEAMS = {
"teams": [
{"id": "team1", "name": "Team 1", "directory": "Team1", "discord_channels": []},
{"id": "team2", "name": "Team 2", "directory": "Team2", "discord_channels": []},
],
"channel_mapping": {"123456": "team1", "789012": "team2"},
}


SAMPLE_REGISTRY = {
"agents": {
"orchestrator": {
"name": "Orchestrateur",
"llm": "claude-sonnet",
"temperature": 0.2,
"max_tokens": 4096,
"prompt": "orchestrator.md",
"type": "orchestrator",
},
"requirements_analyst": {
"name": "Analyste",
"llm": "claude-sonnet",
"temperature": 0.3,
"max_tokens": 32768,
"prompt": "requirements_analyst.md",
"type": "pipeline",
"pipeline_steps": ["analyse", "redaction", "validation"],
},
"lead_dev": {
"name": "Lead Dev",
"llm": "claude-sonnet",
"temperature": 0.3,
"max_tokens": 32768,
"prompt": "lead_dev.md",
"type": "single",
"use_tools": True,
"requires_approval": False,
},
"architect": {
"name": "Architecte",
"llm": "gpt-4o",
"temperature": 0.2,
"max_tokens": 16384,
"prompt": "architect.md",
"type": "single",
},
},
}


SAMPLE_MCP_ACCESS = {
"lead_dev": ["github", "notion"],
"architect": [],
}


SAMPLE_LLM_PROVIDERS = {
"providers": {
"claude-sonnet": {
"type": "anthropic",
"model": "claude-sonnet-4-5-20250929",
"description": "Claude Sonnet",
"env_key": "ANTHROPIC_API_KEY",
},
"gpt-4o": {
"type": "openai",
"model": "gpt-4o",
"description": "GPT-4o",
"env_key": "OPENAI_API_KEY",
},
"ollama-llama3": {
"type": "ollama",
"model": "llama3",
"description": "Llama 3 local",
"base_url": "http://localhost:11434",
},
},
"default": "claude-sonnet",
"throttling": {
"ANTHROPIC_API_KEY": {"rpm": 50, "tpm": 100000},
"OPENAI_API_KEY": {"rpm": 60, "tpm": 150000},
},
}


def _do_clear_caches():
"""Fonction utilitaire pour vider les caches module-level."""
# workflow_engine
try:
from Agents.Shared import workflow_engine
workflow_engine._workflows = {}
except Exception:
pass
# rate_limiter
try:
from Agents.Shared import rate_limiter
rate_limiter._throttling_config = None
rate_limiter._throttles = {}
except Exception:
pass
# llm_provider
try:
from Agents.Shared import llm_provider
llm_provider._providers_config = None
except Exception:
pass
# team_resolver
try:
from Agents.Shared import team_resolver
team_resolver._configs_dir = None
team_resolver._teams_dir = None
team_resolver._teams_config = None
except Exception:
pass
# agent_loader
try:
from Agents.Shared import agent_loader
agent_loader._teams_agents = {}
except Exception:
pass
# event_bus singleton
try:
from Agents.Shared import event_bus
event_bus.EventBus._instance = None
except Exception:
pass


@pytest.fixture(autouse=True)
def _clear_module_caches():
"""Vide les caches module-level avant et apres chaque test."""
_do_clear_caches()
yield
_do_clear_caches()



@pytest.fixture
def sample_workflow():
return SAMPLE_WORKFLOW.copy()


@pytest.fixture
def sample_teams():
return SAMPLE_TEAMS.copy()


@pytest.fixture
def sample_registry():
return SAMPLE_REGISTRY.copy()


@pytest.fixture
def sample_llm_providers():
return SAMPLE_LLM_PROVIDERS.copy()


@pytest.fixture
def tmp_config_dir(tmp_path):
"""Cree une arborescence config/ temporaire avec les fixtures."""
config_dir = tmp_path / "config"
teams_dir = config_dir / "Teams"
team1_dir = teams_dir / "Team1"
team1_dir.mkdir(parents=True)

(teams_dir / "teams.json").write_text(json.dumps(SAMPLE_TEAMS))
(teams_dir / "llm_providers.json").write_text(json.dumps(SAMPLE_LLM_PROVIDERS))
(team1_dir / "agents_registry.json").write_text(json.dumps(SAMPLE_REGISTRY))
(team1_dir / "agent_mcp_access.json").write_text(json.dumps(SAMPLE_MCP_ACCESS))
(team1_dir / "Workflow.json").write_text(json.dumps(SAMPLE_WORKFLOW))

return config_dir
Empty file addedtests/hitl/__init__.py
Empty file.
Loading