118 lines
4.4 KiB
Python
118 lines
4.4 KiB
Python
|
|
"""§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
|