Some checks failed
security-audit / Bandit (scan statique) (push) Successful in 12s
security-audit / pip-audit (CVE dépendances) (push) Successful in 9s
security-audit / Scan secrets (grep) (push) Successful in 8s
tests / Lint (ruff + black) (push) Successful in 13s
tests / Tests unitaires (sans GPU) (push) Failing after 14s
tests / Tests sécurité (critique) (push) Has been skipped
Tests ORALoop init, Decision, reason_workflow_step (click, type, hotkey, wait, passthrough), verify (none, wait, done), run_workflow (empty, too_many), run_instruction (méthodes existent). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
153 lines
4.8 KiB
Python
153 lines
4.8 KiB
Python
"""Tests unitaires pour la boucle ORA (observe→raisonne→agit)."""
|
|
|
|
import pytest
|
|
from unittest.mock import MagicMock, patch
|
|
from core.execution.observe_reason_act import (
|
|
ORALoop, Observation, Decision, VerificationResult, LoopResult
|
|
)
|
|
|
|
|
|
class TestORALoopInit:
|
|
def test_default_params(self):
|
|
loop = ORALoop()
|
|
assert loop.max_retries == 2
|
|
assert loop.max_steps == 50
|
|
assert loop.verify_level == 'auto'
|
|
|
|
def test_custom_params(self):
|
|
loop = ORALoop(max_retries=5, max_steps=10, verify_level='phash')
|
|
assert loop.max_retries == 5
|
|
assert loop.max_steps == 10
|
|
assert loop.verify_level == 'phash'
|
|
|
|
|
|
class TestDecision:
|
|
def test_click_decision(self):
|
|
d = Decision(
|
|
action='click', target='Enregistrer', value='',
|
|
reasoning='Bouton visible', expected_after='Fichier sauvegardé',
|
|
confidence=0.95
|
|
)
|
|
assert d.action == 'click'
|
|
assert d.done == False
|
|
|
|
def test_done_decision(self):
|
|
d = Decision(
|
|
action='done', target='', value='',
|
|
reasoning='Objectif atteint', expected_after='',
|
|
confidence=1.0, done=True
|
|
)
|
|
assert d.done == True
|
|
|
|
|
|
class TestReasonWorkflowStep:
|
|
def test_click_anchor_step(self):
|
|
loop = ORALoop()
|
|
obs = MagicMock()
|
|
step = {
|
|
'action_type': 'click_anchor',
|
|
'label': 'Clic sur Demo',
|
|
'visual_anchor': {
|
|
'target_text': 'Demo',
|
|
'screenshot': 'base64data',
|
|
'bounding_box': {'x': 100, 'y': 200, 'width': 50, 'height': 30}
|
|
}
|
|
}
|
|
decision = loop.reason_workflow_step(step, obs)
|
|
assert decision.action == 'click'
|
|
assert decision.target == 'Demo'
|
|
|
|
def test_type_text_step(self):
|
|
loop = ORALoop()
|
|
obs = MagicMock()
|
|
step = {
|
|
'action_type': 'type_text',
|
|
'label': 'Saisir URL',
|
|
'parameters': {'text': 'https://youtube.com'}
|
|
}
|
|
decision = loop.reason_workflow_step(step, obs)
|
|
assert decision.action == 'type'
|
|
assert decision.value == 'https://youtube.com'
|
|
|
|
def test_keyboard_shortcut_step(self):
|
|
loop = ORALoop()
|
|
obs = MagicMock()
|
|
step = {
|
|
'action_type': 'keyboard_shortcut',
|
|
'label': 'Ctrl+S',
|
|
'parameters': {'keys': ['ctrl', 's']}
|
|
}
|
|
decision = loop.reason_workflow_step(step, obs)
|
|
assert decision.action == 'hotkey'
|
|
|
|
def test_wait_step(self):
|
|
loop = ORALoop()
|
|
obs = MagicMock()
|
|
step = {
|
|
'action_type': 'wait_for_anchor',
|
|
'label': 'Attente',
|
|
'parameters': {'timeout_ms': 3000}
|
|
}
|
|
decision = loop.reason_workflow_step(step, obs)
|
|
assert decision.action == 'wait'
|
|
|
|
def test_unknown_step_passthrough(self):
|
|
loop = ORALoop()
|
|
obs = MagicMock()
|
|
step = {'action_type': 'custom_action', 'label': 'Action custom'}
|
|
decision = loop.reason_workflow_step(step, obs)
|
|
assert decision.action == 'passthrough'
|
|
|
|
|
|
class TestVerify:
|
|
def test_verify_none_mode(self):
|
|
loop = ORALoop(verify_level='none')
|
|
pre = MagicMock()
|
|
post = MagicMock()
|
|
decision = Decision('click', 'btn', '', '', '', 0.9)
|
|
result = loop.verify(pre, post, decision)
|
|
assert result.success == True
|
|
|
|
def test_verify_wait_action(self):
|
|
loop = ORALoop(verify_level='phash')
|
|
pre = MagicMock()
|
|
post = MagicMock()
|
|
decision = Decision('wait', '', '', '', '', 0.9)
|
|
result = loop.verify(pre, post, decision)
|
|
assert result.success == True
|
|
|
|
def test_verify_done_action(self):
|
|
loop = ORALoop()
|
|
pre = MagicMock()
|
|
post = MagicMock()
|
|
decision = Decision('done', '', '', '', '', 1.0, done=True)
|
|
result = loop.verify(pre, post, decision)
|
|
assert result.success == True
|
|
|
|
|
|
class TestRunWorkflow:
|
|
def test_empty_workflow(self):
|
|
loop = ORALoop()
|
|
result = loop.run_workflow([])
|
|
assert result.success == True
|
|
assert result.steps_completed == 0
|
|
|
|
def test_too_many_steps(self):
|
|
loop = ORALoop(max_steps=5)
|
|
steps = [{'action_type': 'wait', 'parameters': {}} for _ in range(10)]
|
|
result = loop.run_workflow(steps)
|
|
assert result.success == False
|
|
assert 'max_steps' in result.reason.lower() or result.steps_completed <= 5
|
|
|
|
|
|
class TestRunInstruction:
|
|
def test_has_method(self):
|
|
loop = ORALoop()
|
|
assert hasattr(loop, 'run_instruction')
|
|
assert callable(loop.run_instruction)
|
|
|
|
def test_has_reason_instruction(self):
|
|
loop = ORALoop()
|
|
assert hasattr(loop, 'reason_instruction')
|
|
assert callable(loop.reason_instruction)
|