package main import ( "bytes" "image" "image/jpeg" "net/http" "os" "path/filepath" "strings" "testing" "time" "github.com/stretchr/testify/require" ) func TestTelegramSelectsLargestPhotoAtMost1568Pixels(t *testing.T) { server := newModelFake(t) test := newTestTurnLoop(t, server) telegram := newTelegramFake(t) bot, logs := telegramStartTestBot(t, telegram) picture := imageTestPNG(t, 8, 6) telegram.Image("selected", picture) message := telegramOwnerMessage("") message.Caption = "Read this label.\n" message.Photo = []TelegramPhoto{ { FileID: "small", Width: 100, Height: 200, }, { FileID: "too-large", Width: 1600, Height: 1200, }, { FileID: "selected", Width: 1568, Height: 1176, }, { FileID: "medium", Width: 800, Height: 600, }, } require.NoError(t, bot.handle(t.Context(), test.loop, nil, TelegramUpdate{ UpdateID: 1, Message: message, })) test.events.WaitFor(t, "idle", 1) files, err := os.ReadDir(test.agent.Images().dir) require.NoError(t, err) require.Len(t, files, 1) logged, err := test.memory.Message(0) require.NoError(t, err) require.Equal(t, imageText([]string{files[0].Name()}, message.Caption), logged.Text) require.True(t, strings.HasSuffix(logged.Text, "\n")) require.Contains(t, string(server.Call(0).raw), "input_image") require.NotContains(t, logs.String(), "base64") require.NotContains(t, logs.String(), "Read this label") for _, call := range telegram.Calls() { if call.method == "getFile" { require.Equal(t, "selected", call.params["file_id"]) } } } func TestTelegramImageDocumentsUseContentFormatAndRejectOtherMedia(t *testing.T) { telegram := newTelegramFake(t) png := imageTestPNG(t, 2, 2) var jpegBytes bytes.Buffer require.NoError(t, jpeg.Encode(&jpegBytes, image.NewRGBA(image.Rect(0, 0, 2, 2)), nil)) // A made-up one-pixel lossless WebP image. webp := []byte{'R', 'I', 'F', 'F', 22, 0, 0, 0, 'W', 'E', 'B', 'P', 'V', 'P', '8', 'L', 10, 0, 0, 0, 0x2f, 0, 0, 0, 0, 0x07, 0x10, 0x11, 0x11, 0x88} telegram.Image("png", png) telegram.Image("jpeg", jpegBytes.Bytes()) telegram.Image("webp", webp) telegram.Image("invalid", []byte("not an image")) telegram.Image("large", bytes.Repeat([]byte{0}, imageMaxBytes+1)) api := newTelegramAPI(telegram.URL, telegramTestToken) store := &ImageStore{dir: filepath.Join(t.TempDir(), imageDirName)} testCases := []struct { name string document TelegramDocument suffix string failure string }{ { name: "accepts PNG documents", document: TelegramDocument{ FileID: "png", MimeType: "image/png", }, suffix: ".png]", }, { name: "accepts JPEG documents", document: TelegramDocument{ FileID: "jpeg", MimeType: "image/jpeg", }, suffix: ".jpg]", }, { name: "accepts WebP documents", document: TelegramDocument{ FileID: "webp", MimeType: "image/webp", }, suffix: ".webp]", }, { name: "other documents keep text answer", document: TelegramDocument{ FileID: "pdf", MimeType: "application/pdf", }, failure: "only text messages are supported", }, { name: "other images refused", document: TelegramDocument{ FileID: "gif", MimeType: "image/gif", }, failure: "JPEG, PNG, or WebP", }, { name: "invalid image refused", document: TelegramDocument{ FileID: "invalid", MimeType: "image/png", }, failure: "JPEG, PNG, or WebP", }, { name: "large download refused", document: TelegramDocument{ FileID: "large", MimeType: "image/png", }, failure: "exceeds 20 MB", }, { name: "large metadata refused", document: TelegramDocument{ FileID: "not-downloaded", MimeType: "image/png", FileSize: imageMaxBytes + 1, }, failure: "exceeds 20 MB", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { text, err := telegramImageText(t.Context(), api, store, &TelegramMessage{Document: &tc.document}) if tc.failure != "" { require.ErrorContains(t, err, tc.failure) return } require.NoError(t, err) require.True(t, strings.HasSuffix(text, tc.suffix)) }) } } func TestTelegramRetriesImageDownloadBeforeAdvancingOffset(t *testing.T) { telegram := newTelegramFake(t) telegram.Image("photo", imageTestPNG(t, 2, 2)) tries := 0 telegram.fail = func(call TelegramFakeCall) (int, string) { if call.method == "getFile" { tries++ if tries == 1 { return http.StatusServiceUnavailable, "try again" } } return 0, "" } bot, logs := telegramStartTestBot(t, telegram) server := newModelFake(t) socket := daemonStartTestWithBot(t, server, bot) terminal := terminalStartTestClient(t, socket) terminal.WaitFor(t, "[connected to OneChat]") message := telegramOwnerMessage("") message.Photo = []TelegramPhoto{{ FileID: "photo", Width: 2, Height: 2, }} telegram.Add(TelegramUpdate{ UpdateID: 7, Message: message, }) terminal.WaitFor(t, "[done]") require.Eventually(t, func() bool { offset, err := telegramLoadOffset(bot.offsetPath) return err == nil && offset == 8 }, 10*time.Second, time.Millisecond) require.Contains(t, logs.String(), "receive failed") require.Contains(t, logs.String(), "receive back") require.NotContains(t, logs.String(), telegramTestToken) require.NotContains(t, logs.String(), "base64") } func TestTelegramRefusesPhotoWhenEverySizeIsTooLarge(t *testing.T) { telegram := newTelegramFake(t) _, err := telegramImageText(t.Context(), newTelegramAPI(telegram.URL, telegramTestToken), &ImageStore{dir: t.TempDir()}, &TelegramMessage{Photo: []TelegramPhoto{{ FileID: "too-big", Width: 1600, Height: 1000, }}}) require.ErrorContains(t, err, "1568 pixels") require.Empty(t, telegram.Calls()) } func TestTelegramStorageFailureDoesNotAcceptTheImage(t *testing.T) { telegram := newTelegramFake(t) telegram.Image("photo", imageTestPNG(t, 2, 2)) test := newTestTurnLoop(t, newModelFake(t)) require.NoError(t, os.WriteFile(test.agent.Images().dir, []byte("blocked"), 0o600)) bot, _ := telegramStartTestBot(t, telegram) message := telegramOwnerMessage("") message.Photo = []TelegramPhoto{{ FileID: "photo", Width: 2, Height: 2, }} err := bot.handle(t.Context(), test.loop, nil, TelegramUpdate{ UpdateID: 1, Message: message, }) require.ErrorContains(t, err, "create image directory") require.Equal(t, 0, test.memory.Len()) _, found := bot.next() require.False(t, found) }