@@ -18,6 +18,7 @@ import (
1818 "testing"
1919 "time"
2020
21+ "telegram-agent/internal/agent"
2122 "telegram-agent/internal/config"
2223 "telegram-agent/internal/llm"
2324 "telegram-agent/internal/store"
@@ -285,6 +286,172 @@ func TestTGAdminModelSetUpdatesCurrentRoleSlot(t *testing.T) {
285286 }
286287}
287288
289+ type fakeTGOperationsAgent struct {
290+ clearedChatID int64
291+ compactChatID int64
292+ compactErr error
293+ stats store.ChatStats
294+ statsOK bool
295+ tools []agent.ToolInfo
296+ }
297+
298+ func (f * fakeTGOperationsAgent ) ClearHistory (chatID int64 ) {
299+ f .clearedChatID = chatID
300+ }
301+
302+ func (f * fakeTGOperationsAgent ) Compact (_ context.Context , chatID int64 ) error {
303+ f .compactChatID = chatID
304+ return f .compactErr
305+ }
306+
307+ func (f * fakeTGOperationsAgent ) GetStats (int64 ) (store.ChatStats , bool ) {
308+ return f .stats , f .statsOK
309+ }
310+
311+ func (f * fakeTGOperationsAgent ) ListTools () []agent.ToolInfo {
312+ return f .tools
313+ }
314+
315+ type fakeMCPReloader struct {
316+ configs map [string ]config.MCPServerConfig
317+ toolCount int
318+ }
319+
320+ func (f * fakeMCPReloader ) ReloadMCP (_ context.Context , configs map [string ]config.MCPServerConfig ) (int , error ) {
321+ f .configs = configs
322+ return f .toolCount , nil
323+ }
324+
325+ type fakeSettingsStore struct {
326+ values map [string ]string
327+ }
328+
329+ func (f fakeSettingsStore ) GetSetting (_ context.Context , key string ) (string , bool , error ) {
330+ v , ok := f .values [key ]
331+ return v , ok , nil
332+ }
333+
334+ func (f fakeSettingsStore ) PutSetting (context.Context , string , string ) error {
335+ return nil
336+ }
337+
338+ func TestTGAdminStatsReturnsOwnerChatStats (t * testing.T ) {
339+ s := newTestServer (t )
340+ s .cfgRef .Telegram .BotToken = "123:abc"
341+ s .cfgRef .Telegram .OwnerChatID = 42
342+ s .opsAgent = & fakeTGOperationsAgent {
343+ statsOK : true ,
344+ stats : store.ChatStats {
345+ ActiveMessages : 7 ,
346+ ActiveChars : 1234 ,
347+ LastMessageAt : time .Date (2026 , 6 , 14 , 12 , 0 , 0 , 0 , time .UTC ),
348+ },
349+ }
350+
351+ req := httptest .NewRequest (http .MethodGet , "/tg-admin/api/stats" , nil )
352+ req .Header .Set ("X-Telegram-Init-Data" , signedTGInitData (t , "123:abc" , 42 , time .Now ()))
353+ rec := httptest .NewRecorder ()
354+ s .handleTGAdminRouter (rec , req )
355+
356+ if rec .Code != http .StatusOK {
357+ t .Fatalf ("status = %d, want %d, body: %s" , rec .Code , http .StatusOK , rec .Body .String ())
358+ }
359+ var payload tgAdminStats
360+ if err := json .Unmarshal (rec .Body .Bytes (), & payload ); err != nil {
361+ t .Fatal (err )
362+ }
363+ if ! payload .Available || payload .ActiveMessages != 7 || payload .ActiveChars != 1234 {
364+ t .Fatalf ("unexpected stats: %+v" , payload )
365+ }
366+ }
367+
368+ func TestTGAdminToolsReturnsGroupedTools (t * testing.T ) {
369+ s := newTestServer (t )
370+ s .cfgRef .Telegram .BotToken = "123:abc"
371+ s .cfgRef .Telegram .OwnerChatID = 42
372+ s .opsAgent = & fakeTGOperationsAgent {tools : []agent.ToolInfo {
373+ {Name : "search" , ServerName : "memory" },
374+ {Name : "recall" , ServerName : "memory" },
375+ {Name : "query" , ServerName : "finance" },
376+ }}
377+
378+ req := httptest .NewRequest (http .MethodGet , "/tg-admin/api/tools" , nil )
379+ req .Header .Set ("X-Telegram-Init-Data" , signedTGInitData (t , "123:abc" , 42 , time .Now ()))
380+ rec := httptest .NewRecorder ()
381+ s .handleTGAdminRouter (rec , req )
382+
383+ if rec .Code != http .StatusOK {
384+ t .Fatalf ("status = %d, want %d, body: %s" , rec .Code , http .StatusOK , rec .Body .String ())
385+ }
386+ var payload []tgAdminToolGroup
387+ if err := json .Unmarshal (rec .Body .Bytes (), & payload ); err != nil {
388+ t .Fatal (err )
389+ }
390+ if len (payload ) != 2 || payload [0 ].Server != "finance" || payload [1 ].Server != "memory" {
391+ t .Fatalf ("unexpected groups: %+v" , payload )
392+ }
393+ if strings .Join (payload [1 ].Tools , "," ) != "recall,search" {
394+ t .Fatalf ("memory tools not sorted: %+v" , payload [1 ].Tools )
395+ }
396+ }
397+
398+ func TestTGAdminActionRejectsUnknownAction (t * testing.T ) {
399+ s := newTestServer (t )
400+ s .cfgRef .Telegram .BotToken = "123:abc"
401+ s .cfgRef .Telegram .OwnerChatID = 42
402+
403+ req := httptest .NewRequest (http .MethodPost , "/tg-admin/api/action" , strings .NewReader (`{"action":"bad"}` ))
404+ req .Header .Set ("X-Telegram-Init-Data" , signedTGInitData (t , "123:abc" , 42 , time .Now ()))
405+ rec := httptest .NewRecorder ()
406+ s .handleTGAdminRouter (rec , req )
407+
408+ if rec .Code != http .StatusBadRequest {
409+ t .Fatalf ("status = %d, want %d" , rec .Code , http .StatusBadRequest )
410+ }
411+ }
412+
413+ func TestTGAdminClearActionClearsOwnerChat (t * testing.T ) {
414+ s := newTestServer (t )
415+ s .cfgRef .Telegram .BotToken = "123:abc"
416+ s .cfgRef .Telegram .OwnerChatID = 42
417+ ops := & fakeTGOperationsAgent {statsOK : true }
418+ s .opsAgent = ops
419+
420+ req := httptest .NewRequest (http .MethodPost , "/tg-admin/api/action" , strings .NewReader (`{"action":"clear"}` ))
421+ req .Header .Set ("X-Telegram-Init-Data" , signedTGInitData (t , "123:abc" , 42 , time .Now ()))
422+ rec := httptest .NewRecorder ()
423+ s .handleTGAdminRouter (rec , req )
424+
425+ if rec .Code != http .StatusOK {
426+ t .Fatalf ("status = %d, want %d, body: %s" , rec .Code , http .StatusOK , rec .Body .String ())
427+ }
428+ if ops .clearedChatID != 42 {
429+ t .Fatalf ("cleared chat id = %d, want 42" , ops .clearedChatID )
430+ }
431+ }
432+
433+ func TestTGAdminMCPReloadUsesSettingsConfig (t * testing.T ) {
434+ s := newTestServer (t )
435+ s .cfgRef .Telegram .BotToken = "123:abc"
436+ s .cfgRef .Telegram .OwnerChatID = 42
437+ serversJSON := `{"memory":{"type":"http","url":"http://memory:8080"}}`
438+ s .settings = fakeSettingsStore {values : map [string ]string {SettingKeyMCPServers : serversJSON }}
439+ reloader := & fakeMCPReloader {toolCount : 3 }
440+ s .reloader = reloader
441+
442+ req := httptest .NewRequest (http .MethodPost , "/tg-admin/api/action" , strings .NewReader (`{"action":"mcp_reload"}` ))
443+ req .Header .Set ("X-Telegram-Init-Data" , signedTGInitData (t , "123:abc" , 42 , time .Now ()))
444+ rec := httptest .NewRecorder ()
445+ s .handleTGAdminRouter (rec , req )
446+
447+ if rec .Code != http .StatusOK {
448+ t .Fatalf ("status = %d, want %d, body: %s" , rec .Code , http .StatusOK , rec .Body .String ())
449+ }
450+ if len (reloader .configs ) != 1 || reloader .configs ["memory" ].URL != "http://memory:8080" {
451+ t .Fatalf ("unexpected reload configs: %+v" , reloader .configs )
452+ }
453+ }
454+
288455// TestAuthRequired covers the four auth paths + the 401 default.
289456func TestAuthRequired (t * testing.T ) {
290457 s := newTestServer (t )
0 commit comments