Files
resolutionflow/backend/tests/test_internal_ticket_service.py
Michael Chihlas 7a36aeb410 feat(l1): internal_ticket_service with CRUD + status transitions
create_ticket, update_status (sets resolved_at on resolve), get_ticket,
list_tickets_for_account (status filter, account-scoped), promote_to_psa.
Used by L1 intake when account has no PSA integration configured.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 13:11:21 -04:00

183 lines
6.0 KiB
Python

"""Unit + integration tests for internal_ticket_service."""
import uuid
import pytest
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.account import Account
from app.models.user import User
from app.services.internal_ticket_service import (
create_ticket, update_status, get_ticket,
list_tickets_for_account, promote_to_psa,
)
# ---------------------------------------------------------------------------
# Test helpers
# ---------------------------------------------------------------------------
async def _make_account(db: AsyncSession) -> Account:
s = str(uuid.uuid4())[:8]
account = Account(
id=uuid.uuid4(),
name=f"Test Account {s}",
display_code=s[:8],
)
db.add(account)
await db.flush()
return account
async def _make_user(
db: AsyncSession,
*,
account_id: uuid.UUID,
role: str = "l1_tech",
) -> User:
s = str(uuid.uuid4())[:8]
user = User(
id=uuid.uuid4(),
email=f"user-{s}@example.com",
name=f"User {s}",
account_id=account_id,
account_role=role,
role="engineer",
is_active=True,
)
db.add(user)
await db.flush()
return user
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
@pytest.mark.asyncio
async def test_create_ticket_sets_status_open(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
ticket = await create_ticket(
test_db,
account_id=account.id,
created_by_user_id=l1.id,
problem_statement="Outlook can't connect",
customer_name="Alice",
)
assert ticket.status == 'open'
assert ticket.account_id == account.id
assert ticket.customer_name == "Alice"
assert ticket.created_by_user_id == l1.id
@pytest.mark.asyncio
async def test_update_status_to_resolved_sets_resolved_at(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
ticket = await create_ticket(
test_db,
account_id=account.id,
created_by_user_id=l1.id,
problem_statement="Test",
)
assert ticket.resolved_at is None
updated = await update_status(
test_db,
ticket_id=ticket.id,
status='resolved',
resolution_notes="Fixed via reboot",
)
assert updated.status == 'resolved'
assert updated.resolved_at is not None
assert updated.resolution_notes == "Fixed via reboot"
@pytest.mark.asyncio
async def test_update_status_to_escalated_does_not_set_resolved_at(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
ticket = await create_ticket(
test_db, account_id=account.id, created_by_user_id=l1.id,
problem_statement="x",
)
updated = await update_status(test_db, ticket_id=ticket.id, status='escalated')
assert updated.status == 'escalated'
assert updated.resolved_at is None
@pytest.mark.asyncio
async def test_update_status_assigns_user(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
engineer = await _make_user(test_db, account_id=account.id, role="engineer")
ticket = await create_ticket(
test_db, account_id=account.id, created_by_user_id=l1.id,
problem_statement="x",
)
updated = await update_status(
test_db, ticket_id=ticket.id, status='escalated',
assigned_user_id=engineer.id,
)
assert updated.assigned_user_id == engineer.id
@pytest.mark.asyncio
async def test_get_ticket_returns_none_for_missing_id(test_db: AsyncSession):
result = await get_ticket(test_db, ticket_id=uuid.uuid4())
assert result is None
@pytest.mark.asyncio
async def test_list_tickets_filters_by_account(test_db: AsyncSession):
account_a = await _make_account(test_db)
account_b = await _make_account(test_db)
l1_a = await _make_user(test_db, account_id=account_a.id)
l1_b = await _make_user(test_db, account_id=account_b.id)
ticket_a = await create_ticket(
test_db, account_id=account_a.id, created_by_user_id=l1_a.id,
problem_statement="A",
)
ticket_b = await create_ticket(
test_db, account_id=account_b.id, created_by_user_id=l1_b.id,
problem_statement="B",
)
rows = await list_tickets_for_account(test_db, account_id=account_a.id)
ids = [r.id for r in rows]
assert ticket_a.id in ids
assert ticket_b.id not in ids
@pytest.mark.asyncio
async def test_list_tickets_filters_by_status(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
open_t = await create_ticket(
test_db, account_id=account.id, created_by_user_id=l1.id,
problem_statement="open",
)
resolved_t = await create_ticket(
test_db, account_id=account.id, created_by_user_id=l1.id,
problem_statement="r",
)
await update_status(test_db, ticket_id=resolved_t.id, status='resolved')
open_rows = await list_tickets_for_account(test_db, account_id=account.id, status='open')
assert open_t.id in [r.id for r in open_rows]
assert resolved_t.id not in [r.id for r in open_rows]
@pytest.mark.asyncio
async def test_promote_to_psa_sets_external_id(test_db: AsyncSession):
account = await _make_account(test_db)
l1 = await _make_user(test_db, account_id=account.id)
ticket = await create_ticket(
test_db, account_id=account.id, created_by_user_id=l1.id,
problem_statement="x",
)
updated = await promote_to_psa(test_db, ticket_id=ticket.id, psa_ticket_id="CW-12345")
assert updated.psa_promoted_ticket_id == "CW-12345"
@pytest.mark.asyncio
async def test_update_status_raises_for_missing_ticket(test_db: AsyncSession):
with pytest.raises(ValueError, match="not found"):
await update_status(test_db, ticket_id=uuid.uuid4(), status='resolved')