""" This program has been developed by students from the bachelor Computer Science at Utrecht University within the Software Project course. © Copyright Utrecht University (Department of Information and Computing Sciences) """ import uuid import pytest from control_backend.agents.bdi.agentspeak_ast import AstProgram from control_backend.agents.bdi.agentspeak_generator import AgentSpeakGenerator from control_backend.schemas.program import ( BasicNorm, ConditionalNorm, Gesture, GestureAction, Goal, InferredBelief, KeywordBelief, LLMAction, LogicalOperator, Phase, Plan, Program, SemanticBelief, SpeechAction, Trigger, ) @pytest.fixture def generator(): return AgentSpeakGenerator() def test_generate_empty_program(generator): prog = Program(phases=[]) code = generator.generate(prog) assert 'phase("end").' in code assert "!notify_cycle" in code def test_generate_basic_norm(generator): norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="be nice") phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[]) prog = Program(phases=[phase]) code = generator.generate(prog) assert f'norm("be nice") :- phase("{phase.id}").' in code def test_generate_critical_norm(generator): norm = BasicNorm(id=uuid.uuid4(), name="n1", norm="safety", critical=True) phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[]) prog = Program(phases=[phase]) code = generator.generate(prog) assert f'critical_norm("safety") :- phase("{phase.id}").' in code def test_generate_conditional_norm(generator): cond = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="please") norm = ConditionalNorm(id=uuid.uuid4(), name="n1", norm="help", condition=cond) phase = Phase(id=uuid.uuid4(), norms=[norm], goals=[], triggers=[]) prog = Program(phases=[phase]) code = generator.generate(prog) assert 'norm("help")' in code assert 'keyword_said("please")' in code assert f"force_norm_{generator._slugify_str(norm.norm)}" in code def test_generate_goal_and_plan(generator): action = SpeechAction(id=uuid.uuid4(), name="s1", text="hello") plan = Plan(id=uuid.uuid4(), name="p1", steps=[action]) # IMPORTANT: can_fail must be False for +achieved_ belief to be added goal = Goal(id=uuid.uuid4(), name="g1", description="desc", plan=plan, can_fail=False) phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[]) prog = Program(phases=[phase]) code = generator.generate(prog) # Check trigger for goal goal_slug = generator._slugify_str(goal.name) assert f"+!{goal_slug}" in code assert f'phase("{phase.id}")' in code assert '!say("hello")' in code # Check success belief addition assert f"+achieved_{goal_slug}" in code def test_generate_subgoal(generator): subplan = Plan(id=uuid.uuid4(), name="p2", steps=[]) subgoal = Goal(id=uuid.uuid4(), name="sub1", description="sub", plan=subplan) plan = Plan(id=uuid.uuid4(), name="p1", steps=[subgoal]) goal = Goal(id=uuid.uuid4(), name="g1", description="main", plan=plan) phase = Phase(id=uuid.uuid4(), norms=[], goals=[goal], triggers=[]) prog = Program(phases=[phase]) code = generator.generate(prog) subgoal_slug = generator._slugify_str(subgoal.name) # Main goal calls subgoal assert f"!{subgoal_slug}" in code # Subgoal plan exists assert f"+!{subgoal_slug}" in code def test_generate_trigger(generator): cond = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc") plan = Plan(id=uuid.uuid4(), name="p1", steps=[]) trigger = Trigger(id=uuid.uuid4(), name="t1", condition=cond, plan=plan) phase = Phase(id=uuid.uuid4(), norms=[], goals=[], triggers=[trigger]) prog = Program(phases=[phase]) code = generator.generate(prog) # Trigger logic is added to check_triggers assert f"{generator.slugify(cond)}" in code assert f'notify_trigger_start("{generator.slugify(trigger)}")' in code assert f'notify_trigger_end("{generator.slugify(trigger)}")' in code def test_phase_transition(generator): phase1 = Phase(id=uuid.uuid4(), name="p1", norms=[], goals=[], triggers=[]) phase2 = Phase(id=uuid.uuid4(), name="p2", norms=[], goals=[], triggers=[]) prog = Program(phases=[phase1, phase2]) code = generator.generate(prog) assert "transition_phase" in code assert f'phase("{phase1.id}")' in code assert f'phase("{phase2.id}")' in code assert "force_transition_phase" in code def test_astify_gesture(generator): gesture = Gesture(type="single", name="wave") action = GestureAction(id=uuid.uuid4(), name="g1", gesture=gesture) ast = generator._astify(action) assert str(ast) == 'gesture("single", "wave")' def test_astify_llm_action(generator): action = LLMAction(id=uuid.uuid4(), name="l1", goal="be funny") ast = generator._astify(action) assert str(ast) == 'reply_with_goal("be funny")' def test_astify_inferred_belief_and(generator): left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a") right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b") inf = InferredBelief( id=uuid.uuid4(), name="i1", operator=LogicalOperator.AND, left=left, right=right ) ast = generator._astify(inf) assert 'keyword_said("a") & keyword_said("b")' == str(ast) def test_astify_inferred_belief_or(generator): left = KeywordBelief(id=uuid.uuid4(), name="k1", keyword="a") right = KeywordBelief(id=uuid.uuid4(), name="k2", keyword="b") inf = InferredBelief( id=uuid.uuid4(), name="i1", operator=LogicalOperator.OR, left=left, right=right ) ast = generator._astify(inf) assert 'keyword_said("a") | keyword_said("b")' == str(ast) def test_astify_semantic_belief(generator): sb = SemanticBelief(id=uuid.uuid4(), name="s1", description="desc") ast = generator._astify(sb) assert str(ast) == f"semantic_{generator._slugify_str(sb.name)}" def test_slugify_not_implemented(generator): with pytest.raises(NotImplementedError): generator.slugify("not a program element") def test_astify_not_implemented(generator): with pytest.raises(NotImplementedError): generator._astify("not a program element") def test_process_phase_transition_from_none(generator): # Initialize AstProgram manually as we are bypassing generate() generator._asp = AstProgram() # Should safely return doing nothing generator._add_phase_transition(None, None) assert len(generator._asp.plans) == 0