Files
roboco/tests/unit/gateway/test_vault_verbs.py
T

124 lines
4.0 KiB
Python
Raw Normal View History

"""curate_vault content verb — role grant + ContentActions RBAC + flag gating.
Mirrors test_playbook_verbs.py's shape: only the Auditor curates; a
delivery role is refused; the flag off short-circuits before any write.
"""
from __future__ import annotations
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
import pytest
from roboco.config import settings
from roboco.services.gateway.content_actions import ContentActions, ContentActionsDeps
from roboco.services.gateway.role_config import get_role_config
def _actions(role: str) -> ContentActions:
task = MagicMock()
agent = MagicMock()
agent.role = role
task.agent_for = AsyncMock(return_value=agent)
task.session = AsyncMock()
deps = ContentActionsDeps(
task=task,
git=MagicMock(),
a2a=MagicMock(),
journal=MagicMock(),
workspace=MagicMock(),
notifications=MagicMock(),
)
return ContentActions(deps)
def test_auditor_can_curate_vault() -> None:
assert "curate_vault" in get_role_config("auditor").do_tools
def test_developer_cannot_curate_vault() -> None:
assert "curate_vault" not in get_role_config("developer").do_tools
@pytest.mark.asyncio
async def test_curate_vault_forbidden_for_developer(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(settings, "obsidian_vault_enabled", True)
env = await _actions("developer").curate_vault(
agent_id=uuid4(), task_id=uuid4(), narrative="did stuff"
)
assert env.error == "not_authorized"
@pytest.mark.asyncio
async def test_curate_vault_disabled_when_flag_off(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(settings, "obsidian_vault_enabled", False)
env = await _actions("auditor").curate_vault(
agent_id=uuid4(), task_id=uuid4(), narrative="did stuff"
)
assert env.error == "invalid_state"
@pytest.mark.asyncio
async def test_curate_vault_not_found(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(settings, "obsidian_vault_enabled", True)
actions = _actions("auditor")
actions.task.get = AsyncMock(return_value=None)
env = await actions.curate_vault(
agent_id=uuid4(), task_id=uuid4(), narrative="did stuff"
)
assert env.error == "not_found"
@pytest.mark.asyncio
async def test_curate_vault_writes_narrative_for_auditor(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(settings, "obsidian_vault_enabled", True)
actions = _actions("auditor")
task_id = uuid4()
task = MagicMock()
actions.task.get = AsyncMock(return_value=task)
writer = MagicMock()
data = MagicMock()
with (
patch(
"roboco.services.vault_assembly.assemble_task_note_data",
AsyncMock(return_value=data),
) as assemble,
patch("roboco.services.vault_writer.get_vault_writer", return_value=writer),
patch("roboco.services.project.get_project_service"),
):
env = await actions.curate_vault(
agent_id=uuid4(), task_id=task_id, narrative="Shipped after one rework."
)
assert env.error is None
assert env.status == "vault_curated"
assemble.assert_awaited_once()
assert assemble.await_args is not None
assert assemble.await_args.kwargs["narrative"] == "Shipped after one rework."
writer.write_task.assert_called_once_with(data)
@pytest.mark.asyncio
async def test_curate_vault_write_failure_returns_invalid_state(
monkeypatch: pytest.MonkeyPatch,
) -> None:
monkeypatch.setattr(settings, "obsidian_vault_enabled", True)
actions = _actions("auditor")
actions.task.get = AsyncMock(return_value=MagicMock())
with (
patch(
"roboco.services.vault_assembly.assemble_task_note_data",
AsyncMock(side_effect=OSError("disk full")),
),
patch("roboco.services.project.get_project_service"),
):
env = await actions.curate_vault(
agent_id=uuid4(), task_id=uuid4(), narrative="x"
)
assert env.error == "invalid_state"