moo.agent.tests.test_agent_tools

Unit tests for moo.agent.agent_tools — one case per tool against a FakeConnection whose request() records the dispatched command and returns a canned response. RunContext is hand-built per call so the tests do not exercise PydanticAI’s agent loop.

The repo has no pytest-asyncio; coroutine bodies run via asyncio.run().

Functions

make_ctx(deps)

Hand-build a minimal RunContext for direct tool invocation.

run(coro)

Tiny convenience to keep the test bodies one-line.

test_alias_dispatches_with_async_wait(...)

test_all_tools_by_name_keyed_on_advertised_name()

open/close are wrapped via Tool(...) to escape the Python builtin shadowing; the lookup dict must key on the wrapped name, not the function's __name__.

test_all_tools_has_35_entries()

test_all_tools_names_match_original_set()

test_burrow(connection, deps)

test_clear_topic_erases_board_and_book(...)

test_close(connection, deps)

test_create_object_default_parent(...)

test_create_object_explicit_parent(...)

test_describe_normalises_target_and_strips_quotes(...)

test_dig_dispatches_with_async_wait(...)

test_dig_strips_quotes_from_room_name(...)

test_divine_default(connection, deps)

test_divine_with_subject_and_of(connection, deps)

test_done_marks_session_done_and_skips_dispatch(...)

test_drop(connection, deps)

test_exits_defaults_to_here(connection, deps)

test_go_dispatches_synchronously(connection, ...)

test_look_no_target(connection, deps)

test_look_with_target_normalises_int(...)

test_lore_character_miss_coaches_against_inventing(...)

test_lore_character_rejects_source_tag(...)

test_lore_room_returns_brief_from_deps(...)

test_lore_room_without_source_configured(...)

test_move_object(connection, deps)

test_obvious_dispatches_with_async_wait(...)

test_open(connection, deps)

test_page_dispatches_and_records_token_dispatch(...)

test_page_foreman_done_sets_foreman_paged(...)

test_page_non_token_message_does_not_mutate_token_state(...)

test_place(connection, deps)

test_post_board_prefixes_agency_teleport(...)

test_put(connection, deps)

test_raw_empty_command_returns_empty_without_dispatch(...)

test_raw_passes_command_verbatim(connection, ...)

test_read_board_prefixes_agency_teleport(...)

test_read_book_with_room_id(connection, deps)

test_read_book_without_room_id(connection, deps)

test_respond_empty_message_skips_respond_thought(...)

test_respond_second_call_returns_escalation(...)

The second respond() in one cycle returns a stronger nudge.

test_respond_third_call_returns_hard_nudge(...)

The third+ respond() call returns the strong 'stop calling respond()' nudge.

test_respond_thoughtonly_no_dispatch(...)

test_rooms(connection, deps)

test_select_tools_does_not_duplicate_system_tools()

When the whitelist already names raw/respond, they shouldn't appear twice.

test_select_tools_drops_unknown_silently()

test_select_tools_filters_to_whitelist_and_appends_system_tools()

Whitelisted tools + the always-on raw + respond, in declared order.

test_select_tools_none_returns_all()

test_show_defaults_to_here(connection, deps)

test_show_with_target(connection, deps)

test_survey_no_target(connection, deps)

test_survey_with_target(connection, deps)

test_tag_source_accepts_named_ref(...)

test_tag_source_rejects_unknown_slug_when_lore_present(...)

test_tag_source_sanitizes_quotes_in_sources(...)

test_tag_source_sets_list_property_on_numeric_ref(...)

test_tag_source_writes_valid_and_ignores_bogus(...)

test_take_no_source(connection, deps)

test_take_with_source(connection, deps)

test_teleport_guard_uses_live_state_within_cycle(...)

Chained teleports in the same cycle: the second call must see the room the first landed in, not the entry-snapshot.

test_teleport_normal(connection, deps, state)

test_teleport_skips_when_already_there_by_id(...)

test_teleport_skips_when_already_there_by_name(...)

test_tunnel(connection, deps)

test_write_book_prefixes_agency_teleport(...)

test_write_verb_assembles_shebang_and_json_payload(...)

test_write_verb_honors_custom_on_parent(...)

Classes

FakeConnection([responses, calls])

Records every request() call; returns a canned response per call.

FakeLimiter()

Async no-op limiter — wait() resolves immediately.