Files
gogo-telefon/tests/test_definition_of_done.py

118 lines
4.4 KiB
Python
Raw Permalink Normal View History

"""§16 Definition of Done, software phase:
'a demo where a softphone call books a request end-to-end (agent conversation →
availability from fake partner API request pushed webhook outcome
console-SMS logged) all without any hardware present.'
(The chat-widget + mock-tenant half lives in tests/test_chat_widget.py.)
"""
from sqlalchemy import select
from gogo.agent.llm import LLMResponse, ScriptedLLM, ToolUse, set_llm
from gogo.domain import BookingStatus
from gogo.models import BookingRequest, Call, CallRegistration, Service
from gogo.voice.call import CallLimits, CallSession
from tests.fake_partner import staff_resolve
from tests.test_voice import UTTERANCE, FakeCallerTransport, frames, voice_fakes # noqa: F401
SLOT = {"start": "2026-07-15T17:00:00+02:00", "end": "2026-07-15T17:45:00+02:00"}
async def test_definition_of_done_voice_partner_flow(
session, partner_tenant, wire_fake_partner, gogo_client, emails, sms, voice_fakes # noqa: F811
):
stt, tts = voice_fakes
wire_fake_partner.slots["SRV-12"] = [SLOT]
svc = (
await session.execute(select(Service).where(Service.tenant_id == partner_tenant.id))
).scalar_one()
# the "softphone caller" asks for a manicure; the agent checks availability
# against the partner API and pushes the booking request into it
stt.texts = ["Htjela bih manikir u srijedu, Amra Hodžić, broj s kojeg zovem."]
set_llm(
ScriptedLLM(
[
LLMResponse(
text="",
tool_uses=[
ToolUse(
id="t1",
name="check_availability",
input={
"service_id": str(svc.id),
"date_from": "2026-07-15",
"date_to": "2026-07-15",
},
)
],
stop_reason="tool_use",
),
LLMResponse(
text="",
tool_uses=[
ToolUse(
id="t2",
name="submit_booking_request",
input={
"service_id": str(svc.id),
"service_name_raw": "manikir",
"client_name": "Amra Hodžić",
"client_phone": "+38765123456",
"slots": [SLOT],
"summary": "Manikir, srijeda u 17.",
},
)
],
stop_reason="tool_use",
),
LLMResponse(
text="Prosljeđujem zahtjev salonu — kontaktiraće vas. Hvala i prijatno!"
),
]
)
)
reg = CallRegistration(tenant_id=partner_tenant.id, caller_msisdn="+38765123456")
session.add(reg)
await session.commit()
reg_id = reg.id
transport = FakeCallerTransport(UTTERANCE)
await CallSession(
transport, reg_id, pace=0, limits=CallLimits(inactivity_s=5, max_call_s=60)
).run()
set_llm(None)
# 1) availability came from the fake partner, request was PUSHED into it
assert "REQ-0001" in wire_fake_partner.requests
pushed = wire_fake_partner.requests["REQ-0001"]
assert pushed["client"]["name"] == "Amra Hodžić"
assert pushed["service_id"] == "SRV-12"
assert pushed["source"] == "voice"
# partner tenants get no owner email by default (§8.2)
assert emails == []
# 2) salon staff confirm in their own software → webhook → Gogo closes + SMS
resp = await staff_resolve(
gogo_client, str(partner_tenant.id), "REQ-0001", "confirmed", confirmed_slot=SLOT
)
assert resp.status_code == 200
session.expire_all()
req = (await session.execute(select(BookingRequest))).scalar_one()
assert req.status == BookingStatus.confirmed.value
# 3) console-SMS logged to the client
assert len(sms) == 1
to, body = sms[0]
assert to == "+38765123456"
assert "Potvrđen termin" in body and "srijeda" in body
# 4) the call itself is fully accounted for
call = (await session.execute(select(Call))).scalar_one()
assert call.outcome == "request_created"
assert call.transcript and call.recording_path