Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions apps/discord_bot/src/five08/discord_bot/cogs/invoices.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
STATUS_LABEL = {0: "Draft", 1: "Submitted", 2: "Cancelled"}

# Invoice access rules — a caller may validate an invoice if any of these hold:
# 1. They have Steering Committee role or above (full access).
# 1. They have a privileged role (one of _PRIVILEGED_ROLES) for full access.
# 2. They created the invoice (invoice owner matches one of their ERP emails).
# 3. They are on the invoice's ERP project roster.
_PRIVILEGED_ROLES = ["Steering Committee"]
_PRIVILEGED_ROLES = ["Steering Committee", "Workflows Engineer"]


def _can_view_invoice(
Expand Down Expand Up @@ -115,8 +115,9 @@ async def validate_invoice_command(
self._resolve_access, interaction
)
if not include_all and not emails:
roles_str = " or ".join(_PRIVILEGED_ROLES)
await interaction.followup.send(
"Invoice validation is available to Steering Committee members "
f"Invoice validation is restricted to {roles_str} "
"or confirmed ERP project members.",
ephemeral=True,
)
Comment thread
lairwaves marked this conversation as resolved.
Expand Down
23 changes: 21 additions & 2 deletions tests/unit/test_invoices_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import pytest

from five08.discord_bot.cogs.invoices import InvoicesCog
from five08.discord_bot.cogs.invoices import InvoicesCog, _PRIVILEGED_ROLES
from five08.clients.erpnext import ERPNextAPIError


Expand Down Expand Up @@ -45,6 +45,12 @@ def mock_interaction() -> AsyncMock:
return _make_interaction(role_names=["Steering Committee"], user_id=1001)


@pytest.fixture
def mock_workflows_engineer_interaction() -> AsyncMock:
"""A privileged (Workflows Engineer) caller with full invoice access."""
return _make_interaction(role_names=["Workflows Engineer"], user_id=1002)


@pytest.fixture
def mock_member_interaction() -> AsyncMock:
"""A non-privileged caller subject to owner/project access rules."""
Expand Down Expand Up @@ -148,10 +154,23 @@ async def test_validate_invoice_denied_without_erp_identity(
cog, mock_member_interaction, mock_doctype, "TEST-SINV-0001"
)
sent = mock_member_interaction.followup.send.call_args.args[0]
assert "Steering Committee" in sent
assert all(role in sent for role in _PRIVILEGED_ROLES)
cog.client.get_invoice.assert_not_called()


@pytest.mark.asyncio
async def test_validate_invoice_allowed_for_workflows_engineer(
cog, mock_workflows_engineer_interaction, mock_doctype
):
"""Workflows Engineer gets include_all access without needing an ERP identity."""
cog.client.get_invoice = Mock(return_value=VALID_INVOICE)
await cog.validate_invoice_command.callback(
cog, mock_workflows_engineer_interaction, mock_doctype, "TEST-SINV-0001"
)
embed = mock_workflows_engineer_interaction.followup.send.call_args.kwargs["embed"]
assert "No issues found" in embed.title


@pytest.mark.asyncio
async def test_validate_invoice_allowed_for_invoice_owner(
cog, mock_member_interaction, mock_doctype
Expand Down