package bot import ( "context" "errors" "net" "testing" "time" iptest "git.faercol.me/faercol/public-ip-tracker/tracker/ip/test" "github.com/ahugues/go-telegram-api/structs" "github.com/google/uuid" ) const expectedChatID = 42 // Need to mock the telegram bot, because no mock is provided by my own lib, what a shame. type mockTGBot struct { SendMessageProp error SendMessageFunc func(context.Context, int64, string, structs.FormattingOption) error } // Do nothing here, it's not used by this bot func (mb *mockTGBot) GetMe(ctx context.Context) (structs.User, error) { return structs.User{}, nil } func (mb *mockTGBot) SendMessage(ctx context.Context, chatID int64, content string, option structs.FormattingOption) error { if mb.SendMessageFunc == nil { return mb.SendMessageProp } return mb.SendMessageFunc(ctx, chatID, content, option) } // Need to mock the notifier here too, but will do later on, I need to improve my lib in the future type mockTGNotifier struct { } func (mn *mockTGNotifier) Run(ctx context.Context) {} func (mn *mockTGNotifier) Subscribe(eventTypes []structs.UpdateType) (uuid.UUID, <-chan structs.Update) { return uuid.New(), make(chan structs.Update) } func (mn *mockTGNotifier) Unsubscribe(id uuid.UUID) {} func (mn *mockTGNotifier) ErrChan() <-chan error { return nil } func TestInit(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) called := false expectedMsg := "\\[Host `test`\\] Sat, 28 Jan 2023 14:17:12 UTC\nPublic IP tracker initialized\\. Current IP is 198\\.51\\.100\\.42" tgBot := mockTGBot{ SendMessageFunc: func(ctx context.Context, chatID int64, content string, option structs.FormattingOption) error { if option != structs.FormattingMarkdownV2 { t.Errorf("Unexpected formatting option %v", option) } if chatID != expectedChatID { t.Errorf("Unexpected chatID %d", chatID) } if content != expectedMsg { t.Errorf("Unexpected message %s", content) } called = true return nil }, } ipGetter := iptest.TestIPGetter{ PublicIPProp: net.ParseIP("198.51.100.42"), } bot := Notifier{ ctx: ctx, cancel: cancel, tgBot: &tgBot, tgChatID: expectedChatID, timeGetter: func() time.Time { return time.Date(2023, 1, 28, 14, 17, 12, 0, time.UTC) }, ipGetter: &ipGetter, frequency: 1 * time.Minute, tgWatcher: &mockTGNotifier{}, hostname: "test", } if err := bot.SendInitMessage(); err != nil { t.Fatalf("Unexpected error %s", err.Error()) } if !called { t.Error("Telegram bot not called") } } func TestUpdateIP(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) called := false expectedMsg := "\\[Host `test`\\] Sat, 28 Jan 2023 14:17:12 UTC\nPublic IP has changed, new IP is 198\\.51\\.100\\.42" tgBot := mockTGBot{ SendMessageFunc: func(ctx context.Context, chatID int64, content string, option structs.FormattingOption) error { if option != structs.FormattingMarkdownV2 { t.Errorf("Unexpected formatting option %v", option) } if chatID != expectedChatID { t.Errorf("Unexpected chatID %d", chatID) } if content != expectedMsg { t.Errorf("Unexpected message %s", content) } called = true return nil }, } ipGetter := iptest.TestIPGetter{ PublicIPProp: net.ParseIP("198.51.100.42"), } bot := Notifier{ ctx: ctx, cancel: cancel, tgBot: &tgBot, tgChatID: expectedChatID, timeGetter: func() time.Time { return time.Date(2023, 1, 28, 14, 17, 12, 0, time.UTC) }, currentIP: net.ParseIP("198.51.100.12"), exitChan: make(chan struct{}, 1), errChan: make(chan error, 5), ipGetter: &ipGetter, tgWatcher: &mockTGNotifier{}, frequency: 500 * time.Millisecond, hostname: "test", } go bot.Run() select { case <-bot.Exit(): t.Error("Unexpected exit") case <-time.After(1 * time.Second): break } cancel() select { case <-bot.Exit(): break case <-time.After(2 * time.Second): t.Error("Unexpected timeout") } select { case err := <-bot.ErrChan(): t.Errorf("Unexpected error %s", err.Error()) default: break } if !called { t.Error("Telegram bot not called") } } func TestUpdateIPNoChange(t *testing.T) { t.Parallel() ctx, cancel := context.WithCancel(context.Background()) tgBot := mockTGBot{ SendMessageProp: errors.New("should not be called"), } ipGetter := iptest.TestIPGetter{ PublicIPProp: net.ParseIP("198.51.100.42"), } bot := Notifier{ ctx: ctx, cancel: cancel, tgBot: &tgBot, tgChatID: expectedChatID, timeGetter: func() time.Time { return time.Date(2023, 1, 28, 14, 17, 12, 0, time.UTC) }, currentIP: net.ParseIP("198.51.100.42"), exitChan: make(chan struct{}, 1), errChan: make(chan error, 5), ipGetter: &ipGetter, tgWatcher: &mockTGNotifier{}, frequency: 100 * time.Millisecond, } go bot.Run() select { case <-bot.Exit(): t.Error("Unexpected exit") case <-time.After(500 * time.Millisecond): break } cancel() select { case <-bot.Exit(): break case <-time.After(2 * time.Second): t.Error("Unexpected timeout") } select { case err := <-bot.ErrChan(): t.Errorf("Unexpected error %s", err.Error()) default: break } }