"""§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