mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
124 lines
4.0 KiB
Python
124 lines
4.0 KiB
Python
"""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"
|