using System.Text.Json.Nodes; using backend.Conversations; using backend.Data; using backend.Realtime; using backend.Tools; using FluentAssertions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Xunit; namespace backend.tests; public class RealtimeSessionToolsTests(ApiFactory factory) : IClassFixture { private readonly ApiFactory _factory = factory; [Fact] public async Task Get_current_time_call_returns_function_call_output_and_persists_tool_turn() { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var log = scope.ServiceProvider.GetRequiredService(); var reg = new ToolRegistry(new ITool[] { new GetCurrentTimeTool() }); var upstream = new ScriptedRealtimeUpstream(); var sink = new RecordingSink(); var cfg = new RealtimeSettings("m", "v", "p", 30, new HashSet { "get_current_time" }); var session = new RealtimeSession(Guid.NewGuid(), upstream, sink, log, reg, cfg, new FakeDeviceChannel(), NullLogger.Instance); upstream.Push(new JsonObject { ["type"] = "session.updated" }); var cts = new CancellationTokenSource(); var run = session.RunAsync(cts.Token); await sink.WaitForAsync("session_started", TimeSpan.FromSeconds(2)); upstream.Push(new JsonObject { ["type"] = "response.function_call_arguments.done", ["call_id"] = "call_abc", ["name"] = "get_current_time", ["arguments"] = "{}", }); upstream.Push(new JsonObject { ["type"] = "response.done" }); var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(2); while (DateTime.UtcNow < deadline && !upstream.Sent.Any(e => (string?)e["type"] == "conversation.item.create")) { await Task.Delay(20); } var item = upstream.Sent.First(e => (string?)e["type"] == "conversation.item.create"); ((string?)item["item"]!["type"]).Should().Be("function_call_output"); ((string?)item["item"]!["call_id"]).Should().Be("call_abc"); ((string?)item["item"]!["output"]).Should().Contain("\"now\""); upstream.Sent.Should().Contain(e => (string?)e["type"] == "response.create"); var convoId = session.ConversationId!.Value; var toolTurn = await db.Turns.FirstAsync(t => t.ConversationId == convoId && t.Role == TurnRole.Tool); toolTurn.ToolName.Should().Be("get_current_time"); toolTurn.ToolResultJson.Should().Contain("\"now\""); cts.Cancel(); await run; } }