/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/chat/livelocation_test.go
439 строк
12 KB
chrisnojima-zoom
Version 670 - clean2 (#29122)
08 июн 2026, 19:31
Не верифицирован
08 июн 2026, 19:31
1943197
Код
Авторство
О чём код?
package chat import ( "context" "net/url" "strconv" "testing" "time" "github.com/keybase/client/go/chat/commands" "github.com/keybase/client/go/chat/globals" "github.com/keybase/client/go/chat/maps" "github.com/keybase/client/go/chat/storage" "github.com/keybase/client/go/chat/types" "github.com/keybase/client/go/chat/utils" "github.com/keybase/client/go/kbtest" "github.com/keybase/client/go/protocol/chat1" "github.com/keybase/client/go/protocol/gregor1" "github.com/keybase/client/go/protocol/keybase1" "github.com/keybase/clockwork" "github.com/stretchr/testify/require" ) type mockChatUI struct { utils.NullChatUI watchID chat1.LocationWatchID watchCh chan chat1.LocationWatchID clearCh chan chat1.LocationWatchID } func newMockChatUI() *mockChatUI { return &mockChatUI{ watchCh: make(chan chat1.LocationWatchID, 10), clearCh: make(chan chat1.LocationWatchID, 10), } } func (m *mockChatUI) ChatWatchPosition(context.Context, chat1.ConversationID, chat1.UIWatchPositionPerm) (chat1.LocationWatchID, error) { m.watchID++ m.watchCh <- m.watchID return m.watchID, nil } func (m *mockChatUI) ChatClearWatch(ctx context.Context, watchID chat1.LocationWatchID) error { m.clearCh <- watchID return nil } func (m *mockChatUI) ChatCommandStatus(context.Context, chat1.ConversationID, string, chat1.UICommandStatusDisplayTyp, []chat1.UICommandStatusActionTyp, ) error { return nil } type unfurlData struct { done bool coords []chat1.Coordinate } type mockUnfurler struct { globals.Contextified types.DummyUnfurler t *testing.T unfurlCh chan unfurlData } var _ types.Unfurler = (*mockUnfurler)(nil) func newMockUnfurler(g *globals.Context, t *testing.T) *mockUnfurler { return &mockUnfurler{ Contextified: globals.NewContextified(g), t: t, unfurlCh: make(chan unfurlData, 10), } } func (m *mockUnfurler) Prefetch(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, msgText string, ) int { return 0 } func (m *mockUnfurler) UnfurlAndSend(ctx context.Context, uid gregor1.UID, convID chat1.ConversationID, msg chat1.MessageUnboxed, ) { require.True(m.t, msg.IsValid()) body := msg.Valid().MessageBody require.True(m.t, body.IsType(chat1.MessageType_TEXT)) mapurl := body.Text().Body u, err := url.Parse(mapurl) require.NoError(m.t, err) livekey := u.Query().Get("livekey") slat := u.Query().Get("lat") slon := u.Query().Get("lon") sdone := u.Query().Get("done") shouldNotify := false if len(livekey) > 0 { done, err := strconv.ParseBool(sdone) require.NoError(m.t, err) shouldNotify = true m.unfurlCh <- unfurlData{ done: done, coords: m.G().LiveLocationTracker.GetCoordinates(ctx, types.LiveLocationKey(livekey)), } } else if len(slat) > 0 { shouldNotify = true lat, err := strconv.ParseFloat(slat, 64) require.NoError(m.t, err) lon, err := strconv.ParseFloat(slon, 64) require.NoError(m.t, err) m.unfurlCh <- unfurlData{ done: true, coords: []chat1.Coordinate{ { Lat: lat, Lon: lon, }, }, } } if !shouldNotify { return } outboxID := storage.GetOutboxIDFromURL(mapurl, convID, msg) mvalid := msg.Valid() mvalid.ClientHeader.OutboxID = &outboxID notMsg := chat1.NewMessageUnboxedWithValid(mvalid) activity := chat1.NewChatActivityWithIncomingMessage(chat1.IncomingMessage{ Message: utils.PresentMessageUnboxed(ctx, m.G(), notMsg, uid, convID), ConvID: convID, }) m.G().NotifyRouter.HandleNewChatActivity(ctx, keybase1.UID(uid.String()), chat1.TopicType_CHAT, &activity, chat1.ChatActivitySource_LOCAL, false) } func checkCoords(t *testing.T, unfurler *mockUnfurler, refcoords []chat1.Coordinate, timeout time.Duration) bool { var dat unfurlData select { case dat = <-unfurler.unfurlCh: require.Equal(t, refcoords, dat.coords) case <-time.After(timeout): require.Fail(t, "no map unfurl") } return dat.done } func updateCoords(t *testing.T, livelocation *maps.LiveLocationTracker, coords []chat1.Coordinate, allCoords []chat1.Coordinate, coordsCh chan struct{}, ) []chat1.Coordinate { for _, c := range coords { livelocation.LocationUpdate(context.TODO(), c) allCoords = append(allCoords, c) } for range coords { select { case <-coordsCh: case <-time.After(20 * time.Second): require.Fail(t, "no coords ack") } } return allCoords } func TestChatSrvLiveLocationCurrent(t *testing.T) { useRemoteMock = false defer func() { useRemoteMock = true }() ctc := makeChatTestContext(t, "TestChatSrvLiveLocationCurrent", 1) defer ctc.cleanup() users := ctc.users() tc := ctc.world.Tcs[users[0].Username] chatUI := newMockChatUI() clock := clockwork.NewFakeClock() tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) timeout := 20 * time.Second conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE) coordsCh := make(chan struct{}, 10) unfurler := newMockUnfurler(tc.Context(), t) tc.ChatG.Unfurler = unfurler livelocation := maps.NewLiveLocationTracker(tc.Context()) livelocation.SetClock(clock) livelocation.TestingCoordsAddedCh = coordsCh tc.ChatG.LiveLocationTracker = livelocation tc.ChatG.CommandsSource.(*commands.Source).SetClock(clock) mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/location", })) select { case <-chatUI.watchCh: case <-time.After(timeout): require.Fail(t, "no watch position call") } coords := []chat1.Coordinate{ { Lat: 40.800348, Lon: -73.968784, }, } updateCoords(t, livelocation, coords, nil, coordsCh) checkCoords(t, unfurler, []chat1.Coordinate{coords[0]}, timeout) clock.Advance(10 * time.Second) select { case <-unfurler.unfurlCh: require.Fail(t, "should not have updated yet") default: } select { case <-chatUI.clearCh: case <-time.After(timeout): require.Fail(t, "no clear call") } } func TestChatSrvLiveLocation(t *testing.T) { useRemoteMock = false defer func() { useRemoteMock = true }() ctc := makeChatTestContext(t, "TestChatSrvLiveLocation", 1) defer ctc.cleanup() users := ctc.users() tc := ctc.world.Tcs[users[0].Username] chatUI := newMockChatUI() clock := clockwork.NewFakeClock() tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) timeout := 20 * time.Second conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE) coordsCh := make(chan struct{}, 10) unfurler := newMockUnfurler(tc.Context(), t) tc.ChatG.Unfurler = unfurler livelocation := maps.NewLiveLocationTracker(tc.Context()) livelocation.SetClock(clock) livelocation.TestingCoordsAddedCh = coordsCh tc.ChatG.LiveLocationTracker = livelocation tc.ChatG.CommandsSource.(*commands.Source).SetClock(clock) // Start up a live location session mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/location live 1h", })) select { case <-chatUI.watchCh: case <-time.After(timeout): require.Fail(t, "no watch position call") } // First update always comes through var allCoords []chat1.Coordinate coords := []chat1.Coordinate{{ Lat: 40.800348, Lon: -73.968784, }} allCoords = updateCoords(t, livelocation, coords, allCoords, coordsCh) checkCoords(t, unfurler, coords, timeout) // Throw some updates in coords = []chat1.Coordinate{ { Lat: 40.798688, Lon: -73.973716, }, { Lat: 40.795234, Lon: -73.976237, }, } allCoords = updateCoords(t, livelocation, coords, allCoords, coordsCh) // no new map yet select { case <-unfurler.unfurlCh: require.Fail(t, "should not have updated yet") default: } // advance clock to get a new map clock.Advance(time.Minute) checkCoords(t, unfurler, allCoords, timeout) // make sure we clear after finishing clock.Advance(2 * time.Hour) select { case <-chatUI.clearCh: case <-time.After(timeout): require.Fail(t, "no clear call") } } func TestChatSrvLiveLocationMultiple(t *testing.T) { useRemoteMock = false defer func() { useRemoteMock = true }() ctc := makeChatTestContext(t, "TestChatSrvLiveLocation", 1) defer ctc.cleanup() users := ctc.users() tc := ctc.world.Tcs[users[0].Username] chatUI := newMockChatUI() clock := clockwork.NewFakeClock() tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) timeout := 20 * time.Second conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE) coordsCh := make(chan struct{}, 10) unfurler := newMockUnfurler(tc.Context(), t) tc.ChatG.Unfurler = unfurler livelocation := maps.NewLiveLocationTracker(tc.Context()) livelocation.SetClock(clock) livelocation.TestingCoordsAddedCh = coordsCh tc.ChatG.LiveLocationTracker = livelocation tc.ChatG.CommandsSource.(*commands.Source).SetClock(clock) var tracker1, tracker2 chat1.LocationWatchID mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/location live 1h", })) select { case tracker1 = <-chatUI.watchCh: case <-time.After(timeout): require.Fail(t, "no watch position call") } mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/location live 3h", })) select { case tracker2 = <-chatUI.watchCh: case <-time.After(timeout): require.Fail(t, "no watch position call") } var allCoords []chat1.Coordinate coords := []chat1.Coordinate{{ Lat: 40.800348, Lon: -73.968784, }} allCoords = updateCoords(t, livelocation, coords, allCoords, coordsCh) checkCoords(t, unfurler, coords, timeout) checkCoords(t, unfurler, coords, timeout) clock.Advance(2 * time.Hour) select { case watchID := <-chatUI.clearCh: require.Equal(t, tracker1, watchID) case <-time.After(timeout): require.Fail(t, "no clear call") } select { case <-chatUI.clearCh: require.Fail(t, "only one tracker should die") default: } // trackers fire after time moves up done := checkCoords(t, unfurler, coords, timeout) if !done { checkCoords(t, unfurler, coords, timeout) // tracker 1 expires and posts again } select { case <-unfurler.unfurlCh: require.Fail(t, "no more unfurls here") default: } coords = []chat1.Coordinate{ { Lat: 40.798688, Lon: -73.973716, }, { Lat: 40.795234, Lon: -73.976237, }, } allCoords = updateCoords(t, livelocation, coords, allCoords, coordsCh) clock.Advance(time.Minute) checkCoords(t, unfurler, allCoords, timeout) select { case <-unfurler.unfurlCh: require.Fail(t, "tracker 1 is done, no update from it") default: } clock.Advance(2 * time.Hour) select { case watchID := <-chatUI.clearCh: require.Equal(t, tracker2, watchID) case <-time.After(timeout): require.Fail(t, "no clear call") } } func TestChatSrvLiveLocationStopTracking(t *testing.T) { useRemoteMock = false defer func() { useRemoteMock = true }() ctc := makeChatTestContext(t, "TestChatSrvLiveLocationStopTracking", 1) defer ctc.cleanup() users := ctc.users() tc := ctc.world.Tcs[users[0].Username] chatUI := newMockChatUI() clock := clockwork.NewFakeClock() tc.G.UIRouter = kbtest.NewMockUIRouter(chatUI) timeout := 20 * time.Second conv := mustCreateConversationForTest(t, ctc, users[0], chat1.TopicType_CHAT, chat1.ConversationMembersType_IMPTEAMNATIVE) coordsCh := make(chan struct{}, 10) unfurler := newMockUnfurler(tc.Context(), t) tc.ChatG.Unfurler = unfurler livelocation := maps.NewLiveLocationTracker(tc.Context()) livelocation.SetClock(clock) livelocation.TestingCoordsAddedCh = coordsCh tc.ChatG.LiveLocationTracker = livelocation tc.ChatG.CommandsSource.(*commands.Source).SetClock(clock) livelocation.Start(context.TODO(), users[0].User.GetUID().ToBytes()) require.False(t, livelocation.ActivelyTracking(context.TODO())) mustPostLocalForTest(t, ctc, users[0], conv, chat1.NewMessageBodyWithText(chat1.MessageText{ Body: "/location live 1h", })) coords := []chat1.Coordinate{{ Lat: 40.800348, Lon: -73.968784, }} updateCoords(t, livelocation, coords, nil, coordsCh) checkCoords(t, unfurler, coords, timeout) livelocation.StopAllTracking(context.TODO()) checkCoords(t, unfurler, coords, timeout) livelocation.Start(context.TODO(), users[0].User.GetUID().ToBytes()) require.False(t, livelocation.ActivelyTracking(context.TODO())) }