Ctrl K
ComunidadGuías…

test/network/test_foundation.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0

"""Foundation integration tests.

Two ``AgentClient``s register through ``LocalLink`` and exchange raw
envelopes; ``Hub.hydrate()`` rebuilds passport/resume/rule caches from
disk. No channels, no LLM, no expectation sweeper — those are
exercised in the higher-level adapter tests.
"""

import pytest

from ag2 import Agent
from ag2.config import AnthropicConfig
from ag2.knowledge import DiskKnowledgeStore, MemoryKnowledgeStore
from ag2.network import (
    EV_TEXT,
    AccessBlock,
    AccessDeniedError,
    Envelope,
    Hub,
    HubClient,
    LimitsBlock,
    LocalLink,
    NotFoundError,
    Passport,
    Resume,
    Rule,
)


def _agent(name: str) -> Agent:
    return Agent(name=name, config=AnthropicConfig(model="claude-sonnet-5"))


# Channel-based envelope round-trip lives in test_consulting.py — once
# adapters are wired all envelope dispatch goes through a real channel.


@pytest.mark.asyncio
async def test_unknown_sender_raises_not_found() -> None:
    """Posting an envelope from an unregistered sender_id raises NotFoundError."""
    store = MemoryKnowledgeStore()
    hub = await Hub.open(store)

    envelope = Envelope(
        channel_id="s1",
        sender_id="ghost",
        audience=["someone"],
        event_type=EV_TEXT,
        event_data={},
    )
    with pytest.raises(NotFoundError):
        await hub.post_envelope(envelope)

    await hub.close()


@pytest.mark.asyncio
async def test_hydrate_reloads_identities_from_disk(tmp_path) -> None:
    """Close hub, reopen with same store, verify identities + resumes survive."""
    store = DiskKnowledgeStore(str(tmp_path))
    hub1 = await Hub.open(store)

    alice = await hub1.register(
        _agent("alice"),
        Passport(name="alice", owner="acme"),
        resume=Resume(claimed_capabilities=["analysis", "research"], summary="senior analyst"),
        skill_md="# Alice\nuse for research",
    )
    bob = await hub1.register(
        _agent("bob"),
        resume=Resume(claimed_capabilities=["debate"]),
        rule=Rule(limits=LimitsBlock(channel_ttl_default="4h")),
    )

    alice_id = alice.agent_id
    bob_id = bob.agent_id

    await hub1.close()

    # Reopen — no shared in-memory state with hub1.
    store2 = DiskKnowledgeStore(str(tmp_path))
    hub2 = await Hub.open(store2)

    alice_p = await hub2.get_agent("alice")
    assert alice_p.agent_id == alice_id
    assert alice_p.owner == "acme"

    bob_p = await hub2.get_agent("bob")
    assert bob_p.agent_id == bob_id

    alice_resume = await hub2.get_resume(alice_id)
    assert "analysis" in alice_resume.claimed_capabilities
    assert alice_resume.summary == "senior analyst"

    alice_skill = await hub2.get_skill(alice_id)
    assert alice_skill is not None
    assert "Alice" in alice_skill

    # Capability filter on the rebuilt index.
    found = await hub2.list_agents(capability="debate")
    assert {p.name for p in found} == {"bob"}

    found_query = await hub2.list_agents(query="senior")
    assert {p.name for p in found_query} == {"alice"}

    await hub2.close()


@pytest.mark.asyncio
async def test_outbound_access_denied() -> None:
    """Sender's outbound_to whitelist blocks the recipient."""
    store = MemoryKnowledgeStore()
    hub = await Hub.open(store)

    alice = await hub.register(
        _agent("alice"),
        rule=Rule(access=AccessBlock(outbound_to=["carol"])),  # bob not allowed
    )
    bob = await hub.register(_agent("bob"))

    envelope = Envelope(
        channel_id="s1",
        sender_id=alice.agent_id,
        audience=[bob.agent_id],
        event_type=EV_TEXT,
        event_data={},
    )
    with pytest.raises(AccessDeniedError):
        await alice.send_envelope(envelope)

    await hub.close()


@pytest.mark.asyncio
async def test_unregister_makes_send_fail() -> None:
    """After unregister, the AgentClient instance is inert.

    The local guard short-circuits before reaching the hub. If a
    different sender_id is used through ``Hub.post_envelope`` directly,
    the hub itself raises ``NotFoundError`` (already covered by
    ``test_unknown_sender_raises_not_found``).
    """
    store = MemoryKnowledgeStore()
    hub = await Hub.open(store)

    alice = await hub.register(_agent("alice"))
    bob = await hub.register(_agent("bob"))

    await alice.unregister()

    envelope = Envelope(
        channel_id="s1",
        sender_id=alice.agent_id,
        audience=[bob.agent_id],
        event_type=EV_TEXT,
        event_data={},
    )
    with pytest.raises(RuntimeError, match="disconnected"):
        await alice.send_envelope(envelope)

    # And the hub itself no longer knows the agent_id.
    with pytest.raises(NotFoundError):
        await hub.get_agent(alice.agent_id)
    remaining_names = {p.name for p in await hub.list_agents()}
    assert remaining_names == {"bob"}

    await hub.close()


@pytest.mark.asyncio
async def test_set_resume_updates_index() -> None:
    """Tenant-driven set_resume swaps the cached resume; queries reflect it."""
    store = MemoryKnowledgeStore()
    hub = await Hub.open(store)
    link = LocalLink(hub)
    hc = HubClient(link, hub=hub)

    alice = await hc.register(_agent("alice"), Passport(name="alice"), Resume())

    new_resume = Resume(claimed_capabilities=["new-skill"], summary="updated")
    await alice.set_resume(new_resume)

    found = await hc.list_agents(capability="new-skill")
    assert {p.name for p in found} == {"alice"}

    fetched = await hc.get_resume(alice.agent_id)
    assert fetched.summary == "updated"
    assert fetched.last_updated != ""

    await hc.close()
    await hub.close()