test: 16 tests unitaires pour la boucle ORA
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
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>
This commit is contained in:
152
tests/unit/test_ora_loop.py
Normal file
152
tests/unit/test_ora_loop.py
Normal file
@@ -0,0 +1,152 @@
|
||||
"""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)
|
||||
Reference in New Issue
Block a user