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

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:
Dom
2026-04-22 10:43:28 +02:00
parent 0ec5e2a25b
commit 00134963e5

152
tests/unit/test_ora_loop.py Normal file
View 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)