feat(I): I-WORK-BASE/I-WORK-ACTOR/I-WORK-HANDOFF — workstream contract (typed blocks, unified posting path, human-agent handoff with task creation), 25 tests passing
This commit is contained in:
@@ -204,3 +204,96 @@ class TestMCPExposure:
|
||||
)
|
||||
assert result["query"] == "test"
|
||||
mock_search.assert_called_once()
|
||||
|
||||
|
||||
# ─── I-WORK-BASE/ACTOR/HANDOFF: Workstream Contract ──────────────────────────
|
||||
|
||||
|
||||
class TestWorkstreamContract:
|
||||
"""Test the workstream contract module (I-WORK-BASE, I-WORK-ACTOR, I-WORK-HANDOFF)."""
|
||||
|
||||
def test_workstream_block_dataclass(self):
|
||||
"""WorkstreamBlock dataclass works correctly."""
|
||||
from app.ai.workstream_contract import WorkstreamBlock
|
||||
block = WorkstreamBlock(type="text", content="Hello", metadata={"key": "value"})
|
||||
assert block.type == "text"
|
||||
assert block.content == "Hello"
|
||||
d = block.to_dict()
|
||||
assert d["type"] == "text"
|
||||
assert d["content"] == "Hello"
|
||||
|
||||
def test_workstream_message_dataclass(self):
|
||||
"""WorkstreamMessage dataclass works correctly."""
|
||||
from app.ai.workstream_contract import WorkstreamMessage, WorkstreamBlock
|
||||
msg = WorkstreamMessage(actor_type="agent", actor_id="abc-123", content="Test", blocks=[WorkstreamBlock(type="text")])
|
||||
assert msg.actor_type == "agent"
|
||||
assert msg.actor_id == "abc-123"
|
||||
d = msg.to_dict()
|
||||
assert d["actor_type"] == "agent"
|
||||
assert len(d["blocks"]) == 1
|
||||
|
||||
def test_build_entity_card(self):
|
||||
"""build_entity_card creates correct block."""
|
||||
from app.ai.workstream_contract import build_entity_card
|
||||
block = build_entity_card("contact", "123", "John Doe", "CEO", "/contacts/123")
|
||||
assert block.type == "entity_card"
|
||||
assert block.metadata["entity_type"] == "contact"
|
||||
assert block.metadata["title"] == "John Doe"
|
||||
|
||||
def test_build_action_card(self):
|
||||
"""build_action_card creates correct block."""
|
||||
from app.ai.workstream_contract import build_action_card
|
||||
block = build_action_card("Approve?", [{"label": "Yes", "action": "approve"}], "Please approve")
|
||||
assert block.type == "action_card"
|
||||
assert len(block.metadata["actions"]) == 1
|
||||
|
||||
def test_build_evidence_card(self):
|
||||
"""build_evidence_card creates correct block."""
|
||||
from app.ai.workstream_contract import build_evidence_card
|
||||
block = build_evidence_card("wiki", "456", "Article", "Snippet", "/wiki/456", 0.9)
|
||||
assert block.type == "evidence_card"
|
||||
assert block.metadata["confidence"] == 0.9
|
||||
|
||||
def test_build_approval_card(self):
|
||||
"""build_approval_card creates correct block."""
|
||||
from app.ai.workstream_contract import build_approval_card
|
||||
block = build_approval_card("appr-123", "send_email", "Please approve")
|
||||
assert block.type == "approval_card"
|
||||
assert block.metadata["approval_id"] == "appr-123"
|
||||
|
||||
def test_build_miniapp_block(self):
|
||||
"""build_miniapp_block creates correct block."""
|
||||
from app.ai.workstream_contract import build_miniapp_block
|
||||
block = build_miniapp_block("calendar-app", "Calendar", {"type": "form"})
|
||||
assert block.type == "miniapp"
|
||||
assert block.metadata["app_id"] == "calendar-app"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_to_workstream_fallback(self):
|
||||
"""post_to_workstream falls back to notification when CommContract unavailable."""
|
||||
from app.ai.workstream_contract import post_to_workstream, WorkstreamMessage, WorkstreamBlock
|
||||
|
||||
with patch("app.core.notifications.post_system_message", new_callable=AsyncMock):
|
||||
result = await post_to_workstream(
|
||||
db=MagicMock(), tenant_id=uuid.uuid4(),
|
||||
message=WorkstreamMessage(actor_type="human", actor_id=str(uuid.uuid4()), content="Test", blocks=[WorkstreamBlock(type="text")]),
|
||||
)
|
||||
assert result is None # Fallback
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_handoff_creates_task(self):
|
||||
"""create_handoff creates a Task with task_type='handoff'."""
|
||||
from app.ai.workstream_contract import create_handoff
|
||||
|
||||
with patch("app.plugins.builtins.tasks.services.create_task", new_callable=AsyncMock) as mock_create:
|
||||
mock_create.return_value = {"id": "task-123", "title": "Handoff: review_needed"}
|
||||
with patch("app.ai.workstream_contract.post_to_workstream", new_callable=AsyncMock):
|
||||
result = await create_handoff(
|
||||
db=MagicMock(), tenant_id=uuid.uuid4(), user_id=uuid.uuid4(),
|
||||
handoff_type="review_needed", description="Please review",
|
||||
)
|
||||
assert result["task"]["id"] == "task-123"
|
||||
mock_create.assert_called_once()
|
||||
# Verify task_type is 'handoff'
|
||||
call_args = mock_create.call_args
|
||||
assert call_args[0][3]["task_type"] == "handoff"
|
||||
|
||||
Reference in New Issue
Block a user