Files
Playa/tests/Volcanic.AgenticCore.Telegram.Tests/InboundComposerTests.cs
EugeneTes a0cd2975b8 feat(telegram): save documents, videos and audio to the chat inbox
Content step 2 (InboundMedia.AddFilesAsync, InboxFiles): files land in <cwd>/inbox/<date>-<safe name>[-N], image documents within Telegram:InlineImageMaxBytes are attached inline, oversize files and failed downloads get a notice and no run. Documents, videos, audio and voice are accepted; the unsupported reply is now 'I can read text, photos, files and voice messages.'

Spec: Updates helpers are named DocumentMessage/VideoMessage/VoiceMessage/AudioMessage; the getFile 'file is too big' notice reads 'over 20 MB'; the too-large inline note is a parenthesised suffix after the size; size formats spelled out.
2026-09-26 22:09:16 +02:00

90 lines
4.0 KiB
C#

using Telegram.Bot.Requests;
using Volcanic.AgenticCore.Telegram.Inbound;
using Volcanic.AgenticCore.Telegram.Tests.Support;
namespace Volcanic.AgenticCore.Telegram.Tests;
public sealed class InboundComposerTests
{
private static InboundComposer Composer(ChatHost host) => host.Composer;
[Fact]
public async Task Text_becomes_the_body_with_reply_and_forward_headers()
{
await using var host = new ChatHost();
var chat = await host.ChatAsync();
var update = Updates.Forward("Geothermal plant opens", Updates.Channel("Daily News", "Anna K."));
update.Message!.ReplyToMessage = Updates.BotMessage("Which plant do you mean?");
var part = await Composer(host).ComposeAsync(chat, update.Message, CancellationToken.None);
Assert.NotNull(part);
Assert.Equal(update.Message.Id, part!.MessageId);
Assert.Equal(new DateTimeOffset(DateTime.SpecifyKind(update.Message.Date, DateTimeKind.Utc)), part.SentAt);
Assert.Equal("[Reply to your message: \"Which plant do you mean?\"]", part.ReplyHeader);
Assert.Equal("Daily News (Anna K.)", part.ForwardLabel);
Assert.Equal(["Geothermal plant opens"], part.Body);
Assert.Empty(part.Images);
}
[Fact]
public async Task Caption_and_the_largest_photo_become_body_and_image()
{
await using var host = new ChatHost();
var chat = await host.ChatAsync();
var update = Updates.AlbumPhoto("g1", caption: "three views");
update.Message!.Photo = [
new() { FileId = "small", FileUniqueId = "s", Width = 10, Height = 10 },
new() { FileId = "large", FileUniqueId = "l", Width = 100, Height = 100 },
];
var part = await Composer(host).ComposeAsync(chat, update.Message, CancellationToken.None);
Assert.Equal(["three views"], part!.Body);
Assert.Equal("image/jpeg", Assert.Single(part.Images).MimeType);
Assert.Equal("g1", part.MediaGroupId);
Assert.Equal("large", Assert.Single(host.Bot.Requests.OfType<GetFileRequest>()).FileId);
}
[Fact]
public async Task A_failed_photo_download_is_reported_and_a_bare_photo_composes_to_null()
{
await using var host = new ChatHost();
var chat = await host.ChatAsync();
host.Bot.OnSend = r => { if (r is GetFileRequest) throw FakeBotClient.NetworkDown(); };
var part = await Composer(host).ComposeAsync(chat, Updates.Photo().Message!, CancellationToken.None);
await chat.Renderer.DrainAsync().WaitAsync(TimeSpan.FromSeconds(5));
Assert.Null(part);
Assert.Contains(host.Texts, t => t.Contains("I could not download that photo.", StringComparison.Ordinal));
}
[Fact]
public async Task A_captioned_photo_that_fails_to_download_keeps_its_caption()
{
await using var host = new ChatHost();
var chat = await host.ChatAsync();
host.Bot.OnSend = r => { if (r is GetFileRequest) throw FakeBotClient.NetworkDown(); };
var part = await Composer(host).ComposeAsync(chat, Updates.Photo("what is this?").Message!, CancellationToken.None);
Assert.Equal(["what is this?"], part!.Body);
Assert.Empty(part.Images);
}
[Fact]
public void Text_photos_files_and_voice_are_supported()
{
Assert.True(InboundComposer.IsSupported(Updates.Text("hi").Message!));
Assert.True(InboundComposer.IsSupported(Updates.Photo().Message!));
Assert.True(InboundComposer.IsSupported(Updates.DocumentMessage("a.pdf", "application/pdf", 3).Message!));
Assert.True(InboundComposer.IsSupported(Updates.VideoMessage(null, 3).Message!));
Assert.True(InboundComposer.IsSupported(Updates.AudioMessage("a.mp3", "audio/mpeg", 3).Message!));
Assert.True(InboundComposer.IsSupported(Updates.VoiceMessage().Message!));
Assert.False(InboundComposer.IsSupported(Updates.StickerMessage().Message!));
Assert.True(InboundComposer.HasAttachment(Updates.Photo("caption").Message!));
Assert.False(InboundComposer.HasAttachment(Updates.Text("/status").Message!));
}
}