package telegram import ( "context" "net/http" "sync" "testing" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" ) // fakeUpdate builds a minimal telegramUpdate JSON object. func fakeUpdate(id int, text string) map[string]any { return map[string]any{ "update_id": id, "message": map[string]any{ "message_id": id, "from": map[string]any{"id": 1, "first_name": "Tester"}, "chat": map[string]any{"id": 10, "type": "private"}, "date": 1700000000, "text": text, }, } } func TestReadMessages_LongPollOffset(t *testing.T) { t.Parallel() var mu sync.Mutex first := true a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if methodOf(r) != "getUpdates" { http.Error(w, "unexpected method", http.StatusNotFound) return } mu.Lock() firstCall := first first = false mu.Unlock() if firstCall { okResponse(w, []map[string]any{fakeUpdate(1, "one"), fakeUpdate(2, "two")}) return } okResponse(w, []map[string]any{}) })) if err := a.Connect(context.Background()); err != nil { t.Fatalf("Connect: %v", err) } ctx, cancel := context.WithCancel(context.Background()) emitted := make(chan adapter.UnifiedMessage, 4) done := make(chan struct{}) go func() { a.ReadMessages(ctx, func(m adapter.UnifiedMessage) { emitted <- m }) close(done) }() got := make([]adapter.UnifiedMessage, 0, 2) for i := 0; i < 2; i++ { select { case m := <-emitted: got = append(got, m) case <-time.After(3 * time.Second): t.Fatal("timeout waiting for emitted message") } } cancel() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("ReadMessages did not return after ctx cancel") } if len(got) != 2 { t.Fatalf("emitted %d messages, want 2", len(got)) } if got[0].Content != "one" || got[1].Content != "two" { t.Errorf("contents = %q/%q, want one/two", got[0].Content, got[1].Content) } a.mu.RLock() offset := a.offset a.mu.RUnlock() if offset != 3 { t.Errorf("offset = %d, want 3", offset) } } func TestReadMessages_CtxPreCanceled(t *testing.T) { t.Parallel() a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "no api call expected", http.StatusNotFound) })) ctx, cancel := context.WithCancel(context.Background()) cancel() done := make(chan struct{}) go func() { a.ReadMessages(ctx, func(m adapter.UnifiedMessage) {}) close(done) }() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("ReadMessages should return immediately on canceled ctx") } } func TestReadMessages_WebhookWaits(t *testing.T) { t.Parallel() var calls int var mu sync.Mutex a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if methodOf(r) == "setWebhook" { okResponse(w, map[string]any{"webhook_url": "https://example.com/hook"}) return } mu.Lock() calls++ mu.Unlock() http.Error(w, "no getUpdates expected in webhook mode", http.StatusNotFound) })) a.cfg.WebhookURL = "https://example.com/hook" if err := a.Connect(context.Background()); err != nil { t.Fatalf("Connect: %v", err) } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { a.ReadMessages(ctx, func(m adapter.UnifiedMessage) {}) close(done) }() time.Sleep(100 * time.Millisecond) cancel() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("ReadMessages should return on cancel in webhook mode") } mu.Lock() if calls != 0 { t.Errorf("getUpdates called %d times in webhook mode", calls) } mu.Unlock() } func TestReadMessages_ErrorRetriesThenCancel(t *testing.T) { t.Parallel() a, _ := newTestAdapter(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if methodOf(r) != "getUpdates" { http.Error(w, "unexpected method", http.StatusNotFound) return } failResponse(w, "Conflict", 409) })) if err := a.Connect(context.Background()); err != nil { t.Fatalf("Connect: %v", err) } ctx, cancel := context.WithCancel(context.Background()) done := make(chan struct{}) go func() { a.ReadMessages(ctx, func(m adapter.UnifiedMessage) {}) close(done) }() time.Sleep(150 * time.Millisecond) cancel() select { case <-done: case <-time.After(2 * time.Second): t.Fatal("ReadMessages should return on cancel despite getUpdates errors") } }