forked from admin/VolcanicAgenticCore
63 lines
2.9 KiB
C#
63 lines
2.9 KiB
C#
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Telegram.Bot.Requests;
|
|
using Volcanic.AgenticCore.Abstractions.Events;
|
|
using Volcanic.AgenticCore.Abstractions.Json;
|
|
using Volcanic.AgenticCore.Abstractions.Messages;
|
|
using Volcanic.AgenticCore.Telegram.Rendering;
|
|
using Volcanic.AgenticCore.Telegram.Tests.Support;
|
|
|
|
namespace Volcanic.AgenticCore.Telegram.Tests;
|
|
|
|
/// <summary>
|
|
/// Replays a session event stream recorded from a real Claude Code run (text, two bash calls, a final answer) through
|
|
/// the renderer with its original timing, sped up four times.
|
|
/// </summary>
|
|
public sealed class ClaudeCodeReplayTests
|
|
{
|
|
private const string Fixture = "Fixtures/claude-code-two-tools.jsonl";
|
|
private const double Speedup = 4;
|
|
|
|
[Fact]
|
|
public async Task Answers_are_sent_once_and_the_working_message_is_gone_at_the_end()
|
|
{
|
|
var bot = new FakeBotClient();
|
|
var options = new TelegramOptions { EditInterval = TimeSpan.FromSeconds(1.5 / Speedup), TypingInterval = TimeSpan.FromSeconds(1) };
|
|
var renderer = new ChatRenderer(1, new TelegramSender(bot, NullLogger<TelegramSender>.Instance), options, NullLogger.Instance);
|
|
|
|
var events = Load();
|
|
var start = DateTime.UtcNow;
|
|
foreach (var (at, e) in events)
|
|
{
|
|
var due = start.AddSeconds(at / Speedup) - DateTime.UtcNow;
|
|
if (due > TimeSpan.Zero) await Task.Delay(due);
|
|
renderer.Post(e);
|
|
}
|
|
await renderer.DrainAsync().WaitAsync(TimeSpan.FromSeconds(10));
|
|
await renderer.DisposeAsync();
|
|
|
|
var answers = events.Select(x => x.Event).OfType<AgentSessionEvent.MessageEnd>()
|
|
.Select(m => m.Message).OfType<AssistantMessage>().Where(m => m.Text.Length > 0)
|
|
.Select(m => TelegramFormatter.ToHtml(m.Text)).ToList();
|
|
Assert.Equal(2, answers.Count);
|
|
|
|
// The chat ends with just the answers, each sent once and never edited (no streaming).
|
|
Assert.Equal(answers, bot.CurrentTexts.OrderBy(kv => kv.Key).Select(kv => kv.Value));
|
|
foreach (var answer in answers) Assert.Single(bot.Sent, s => s.Text == answer);
|
|
Assert.All(bot.Edits, e => Assert.StartsWith("⚙️", e.Text));
|
|
|
|
// The status was not re-sent below the final answer.
|
|
var sends = bot.Sent.Select(s => s.Text).ToList();
|
|
Assert.Equal(answers[^1], sends[^1]);
|
|
Assert.Contains(bot.Edits, e => e.Text!.Contains("✓ <b>bash</b>: <code>pwd</code>", StringComparison.Ordinal));
|
|
Assert.Contains(bot.Requests, r => r is SendChatActionRequest);
|
|
}
|
|
|
|
private static List<(double At, AgentSessionEvent Event)> Load() =>
|
|
File.ReadLines(Fixture).Select(line =>
|
|
{
|
|
using var doc = JsonDocument.Parse(line);
|
|
return (doc.RootElement.GetProperty("t").GetDouble(), doc.RootElement.GetProperty("e").Deserialize<AgentSessionEvent>(VolcanicJson.Options)!);
|
|
}).ToList();
|
|
}
|