Ctrl K
ComunidadGuías…

delivery_resume_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	"bytes"
	"context"
	"fmt"
	"image"
	"image/png"
	"io"
	"net/http"
	"path/filepath"
	"slices"
	"strings"
	"testing"

	"github.com/go-telegram/bot/models"
	"github.com/max-messenger/max-bot-api-client-go/v2/model"
)

type cancelOnClose struct {
	io.ReadCloser
	cancel context.CancelFunc
}

func (b cancelOnClose) Close() error {
	err := b.ReadCloser.Close()
	b.cancel()
	return err
}

func TestTelegramAlbumResumeSendsOnlyItsRemainingText(t *testing.T) {
	ctx, cancel := context.WithCancel(t.Context())
	defer cancel()
	var photo bytes.Buffer
	if err := png.Encode(&photo, image.NewGray(image.Rect(0, 0, 1, 1))); err != nil {
		t.Fatal(err)
	}
	var methods []string
	source := &maxSource{channel: maxChat{id: -42}, to: []*models.ChatFullInfo{{ID: -100}}}
	message := maxMessage{Body: maxMessageBody{Mid: "original", Text: strings.Repeat("я", 5000)}}
	for _, name := range []string{"first", "second"} {
		var media maxMedia
		media.Type = model.AttachImage
		media.Payload.URL = "https://media.example/" + name
		message.Body.Attachments = append(message.Body.Attachments, media)
	}
	r := &relay{
		adminID:  777, // an alert on cancellation would call the deliberately absent client
		mediaDir: t.TempDir(), mediaSlot: make(chan struct{}, 1), maxFileLimit: 1 << 20, maxPostLimit: 2 << 20,
		maxMediaHTTP: &http.Client{Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			return &http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewReader(photo.Bytes())), ContentLength: int64(photo.Len()), Header: make(http.Header), Request: req}, nil
		})},
		tgAPI: telegramAPI{baseURL: "https://telegram.example", token: "token", http: &http.Client{
			Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
				if req.Body != nil {
					_, _ = io.Copy(io.Discard, req.Body)
					_ = req.Body.Close()
				}
				method := strings.TrimPrefix(req.URL.Path, "/bottoken/")
				methods = append(methods, method)
				result := `{"ok":true,"result":{"message_id":13}}`
				if method == "sendMediaGroup" {
					result = `{"ok":true,"result":[{"message_id":11},{"message_id":12}]}`
				}
				body := io.NopCloser(strings.NewReader(result))
				if len(methods) == 1 {
					body = cancelOnClose{body, cancel}
				}
				return &http.Response{StatusCode: http.StatusOK, Status: "200 OK", Body: body, Header: make(http.Header), Request: req}, nil
			}),
		}},
	}
	path := filepath.Join(t.TempDir(), "queue.sqlite")
	db, err := openStore(t.Context(), path)
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { db.close() })
	book := &deliveryBook{store: db, source: channelKey{maxPlatform, -42}, post: "message:original"}
	r.forwardMax(ctx, source, message, book)
	if book.err != nil || !slices.Equal(methods, []string{"sendMediaGroup"}) {
		t.Fatalf("interrupted after album: methods=%v, err=%v", methods, book.err)
	}
	db.close()
	db, err = openStore(t.Context(), path)
	if err != nil {
		t.Fatal(err)
	}
	book = &deliveryBook{store: db, source: channelKey{maxPlatform, -42}, post: "message:original"}
	r.forwardMax(t.Context(), source, message, book)
	if book.err != nil || !slices.Equal(methods, []string{"sendMediaGroup", "sendMessage"}) {
		t.Fatalf("resume duplicated or lost a part: methods=%v, err=%v", methods, book.err)
	}
	var count int
	if err := db.QueryRowContext(t.Context(), `SELECT count(*) FROM mirrors`).Scan(&count); err != nil || count != 3 {
		t.Fatalf("mirrors=%d, err=%v", count, err)
	}
}

func TestMaxTextResumeDoesNotRepublishTheFirstPart(t *testing.T) {
	ctx, cancel := context.WithCancel(t.Context())
	defer cancel()
	db := testStore(t)
	var texts []string
	r := &relay{max: &maxClient{baseURL: "https://max.example", publish: &http.Client{
		Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
			data, err := io.ReadAll(req.Body)
			_ = req.Body.Close()
			if err != nil {
				return nil, err
			}
			texts = append(texts, string(data))
			body := io.NopCloser(strings.NewReader(fmt.Sprintf(`{"message":{"body":{"mid":"part.%d"}}}`, len(texts))))
			if len(texts) == 1 {
				body = cancelOnClose{body, cancel}
			}
			return &http.Response{StatusCode: http.StatusOK, Body: body, Header: make(http.Header), Request: req}, nil
		}),
	}}, adminID: 777}
	source := &source{channel: &models.ChatFullInfo{ID: -100}, to: []maxChat{{id: -42}}}
	p := &post{messages: []*models.Message{{ID: 8, Text: strings.Repeat("а", maxTextLimit) + strings.Repeat("б", 100)}}}
	book := &deliveryBook{store: db, source: channelKey{telegramPlatform, -100}, post: "message:8"}
	r.forward(ctx, source, p, book)
	if book.err != nil || len(texts) != 1 {
		t.Fatalf("first pass: parts=%d, err=%v", len(texts), book.err)
	}
	r.forward(t.Context(), source, p, book)
	if book.err != nil || len(texts) != 2 || texts[0] == texts[1] || !strings.Contains(texts[1], strings.Repeat("б", 100)) {
		t.Fatalf("resume: parts=%d, err=%v", len(texts), book.err)
	}
}