feat: fix Telegram controls and add GitHub links
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codex-helper/internal/security"
|
||||
"codex-helper/internal/store"
|
||||
)
|
||||
|
||||
@@ -48,12 +53,173 @@ func TestSendSMTPStopsWhenServerDoesNotRespond(t *testing.T) {
|
||||
|
||||
func newReminderTestApp(t *testing.T) *App {
|
||||
t.Helper()
|
||||
s, err := store.Open(t.TempDir())
|
||||
dir := t.TempDir()
|
||||
s, err := store.Open(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Cleanup(func() { _ = s.DB.Close() })
|
||||
return &App{store: s, runtimes: map[int64]*accountRuntime{}}
|
||||
vault, err := security.OpenVault(dir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return &App{store: s, vault: vault, runtimes: map[int64]*accountRuntime{}}
|
||||
}
|
||||
|
||||
func TestTGSendKeepsOrRemovesKeyboard(t *testing.T) {
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
var calls []map[string]any
|
||||
tgCall = func(_ string, method string, params any, _ any) error {
|
||||
if method != "sendMessage" {
|
||||
t.Fatalf("method = %q", method)
|
||||
}
|
||||
body, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var decoded map[string]any
|
||||
if err = json.Unmarshal(body, &decoded); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
calls = append(calls, decoded)
|
||||
return nil
|
||||
}
|
||||
if err := tgSend(TelegramSettings{Token: "token", ChatID: 1, MenuEnabled: true}, "on"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tgSend(TelegramSettings{Token: "token", ChatID: 1}, "off"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keyboard := calls[0]["reply_markup"].(map[string]any)
|
||||
removed := calls[1]["reply_markup"].(map[string]any)
|
||||
if keyboard["keyboard"] == nil || removed["remove_keyboard"] != true {
|
||||
t.Fatalf("reply markup mismatch: %#v %#v", keyboard, removed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramDeleteClearsConfigurationAndOffset(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
enc, err := a.vault.Encrypt("secret-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.Set("telegram_token", enc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.SetJSON("telegram", TelegramSettings{ChatID: 0, Enabled: true, MenuEnabled: true, Configured: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.Set("telegram_bind", `{"code":"123456"}`); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err = a.store.DB.Exec("UPDATE telegram_updates SET offset=42 WHERE id=1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramAPI(recorder, httptest.NewRequest(http.MethodDelete, "/api/v1/settings/telegram", nil))
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
for _, key := range []string{"telegram", "telegram_token", "telegram_bind"} {
|
||||
if _, ok := a.store.Get(key); ok {
|
||||
t.Fatalf("setting %q still exists", key)
|
||||
}
|
||||
}
|
||||
var offset int64
|
||||
if err = a.store.DB.QueryRow("SELECT offset FROM telegram_updates WHERE id=1").Scan(&offset); err != nil || offset != 0 {
|
||||
t.Fatalf("offset = %d err = %v", offset, err)
|
||||
}
|
||||
if got := a.telegramSettings(); got.Configured || got.ChatID != 0 || got.Enabled || got.MenuEnabled {
|
||||
t.Fatalf("settings after delete = %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramDeleteWaitsForInFlightSaveAndRemainsFinal(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
getMeStarted := make(chan struct{})
|
||||
releaseGetMe := make(chan struct{})
|
||||
tgCall = func(_ string, method string, _ any, out any) error {
|
||||
if method != "getMe" {
|
||||
t.Fatalf("method = %q", method)
|
||||
}
|
||||
close(getMeStarted)
|
||||
<-releaseGetMe
|
||||
body := []byte(`{"ok":true,"result":{"first_name":"Test","username":"test_bot"}}`)
|
||||
return json.Unmarshal(body, out)
|
||||
}
|
||||
|
||||
putDone := make(chan *httptest.ResponseRecorder, 1)
|
||||
go func() {
|
||||
body := bytes.NewBufferString(`{"token":"new-token","chatId":0,"enabled":true,"menuEnabled":false}`)
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramAPI(recorder, httptest.NewRequest(http.MethodPut, "/api/v1/settings/telegram", body))
|
||||
putDone <- recorder
|
||||
}()
|
||||
<-getMeStarted
|
||||
|
||||
deleteDone := make(chan *httptest.ResponseRecorder, 1)
|
||||
go func() {
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramAPI(recorder, httptest.NewRequest(http.MethodDelete, "/api/v1/settings/telegram", nil))
|
||||
deleteDone <- recorder
|
||||
}()
|
||||
select {
|
||||
case recorder := <-deleteDone:
|
||||
t.Fatalf("delete completed before save: status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
case <-time.After(50 * time.Millisecond):
|
||||
}
|
||||
close(releaseGetMe)
|
||||
if recorder := <-putDone; recorder.Code != http.StatusOK {
|
||||
t.Fatalf("PUT status = %d body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if recorder := <-deleteDone; recorder.Code != http.StatusOK {
|
||||
t.Fatalf("DELETE status = %d body = %s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if _, ok := a.store.Get("telegram_token"); ok {
|
||||
t.Fatal("Telegram token was recreated after delete")
|
||||
}
|
||||
if _, ok := a.store.Get("telegram"); ok {
|
||||
t.Fatal("Telegram settings were recreated after delete")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDisabledTelegramSkipsAutomaticReminderButAllowsManualTest(t *testing.T) {
|
||||
a := newReminderTestApp(t)
|
||||
enc, err := a.vault.Encrypt("secret-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.Set("telegram_token", enc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = a.store.SetJSON("telegram", TelegramSettings{ChatID: 123, Enabled: false, MenuEnabled: false, Configured: true}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
now := time.Now()
|
||||
if _, err = a.store.DB.Exec(`INSERT INTO notifications
|
||||
(dedupe_key,channel,kind,status,attempts,last_error,scheduled_at,sent_at,body)
|
||||
VALUES('disabled-test','configured','before','pending',0,'',?,NULL,'message')`, now.Unix()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
original := tgCall
|
||||
t.Cleanup(func() { tgCall = original })
|
||||
calls := 0
|
||||
tgCall = func(_ string, _ string, _ any, _ any) error {
|
||||
calls++
|
||||
return nil
|
||||
}
|
||||
a.sendPendingReminders(now)
|
||||
if calls != 0 {
|
||||
t.Fatalf("automatic Telegram calls = %d; want 0", calls)
|
||||
}
|
||||
recorder := httptest.NewRecorder()
|
||||
a.telegramTest(recorder, httptest.NewRequest(http.MethodPost, "/api/v1/settings/telegram/test", nil))
|
||||
if recorder.Code != http.StatusOK || calls != 1 {
|
||||
t.Fatalf("manual test status = %d calls = %d body = %s", recorder.Code, calls, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func reminderDashboard(fetchedAt int64, used float64, resetsAt int64) Dashboard {
|
||||
|
||||
Reference in New Issue
Block a user