Skip to content

Commit 830ab56

Browse files
committed
chore: Update tests
BaseServer now receives a pointer since it owns a mutex. + adding more tests for the lifecycle flows
1 parent 03e7404 commit 830ab56

File tree

10 files changed

+311
-21
lines changed

10 files changed

+311
-21
lines changed

pkg/messaging/waku/api_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,54 @@ func TestRunPausedPollingLoopSkipsWhenPausedAndResumes(t *testing.T) {
6363
case tickerCh <- time.Now():
6464
default:
6565
}
66+
return ticks.Load() >= 1
67+
}, time.Second, 20*time.Millisecond)
68+
69+
stopCh <- nil
70+
require.Eventually(t, func() bool {
71+
return len(stopped) == 1
72+
}, time.Second, 20*time.Millisecond)
73+
}
74+
75+
func TestRunPausedPollingLoopStopsWhenLifecycleChannelCloses(t *testing.T) {
76+
lifecycleCh := make(chan bool)
77+
tickerCh := make(chan time.Time, 1)
78+
stopCh := make(chan error, 1)
79+
80+
stopped := make(chan struct{}, 1)
81+
go runPausedPollingLoop(
82+
false,
83+
lifecycleCh,
84+
tickerCh,
85+
stopCh,
86+
func() {},
87+
func() { stopped <- struct{}{} },
88+
)
89+
90+
close(lifecycleCh)
91+
require.Eventually(t, func() bool {
92+
return len(stopped) == 1
93+
}, time.Second, 20*time.Millisecond)
94+
}
95+
96+
func TestRunPausedPollingLoopStopsOnStopSignal(t *testing.T) {
97+
lifecycleCh := make(chan bool, 1)
98+
tickerCh := make(chan time.Time, 1)
99+
stopCh := make(chan error, 1)
100+
101+
var ticks atomic.Int32
102+
stopped := make(chan struct{}, 1)
103+
go runPausedPollingLoop(
104+
false,
105+
lifecycleCh,
106+
tickerCh,
107+
stopCh,
108+
func() { ticks.Add(1) },
109+
func() { stopped <- struct{}{} },
110+
)
111+
112+
tickerCh <- time.Now()
113+
require.Eventually(t, func() bool {
66114
return ticks.Load() == 1
67115
}, time.Second, 20*time.Millisecond)
68116

protocol/communities/manager_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/status-im/status-go/internal/testutils/fake"
2727
"github.com/status-im/status-go/params"
2828
"github.com/status-im/status-go/pkg/messaging"
29+
messaginglifecycle "github.com/status-im/status-go/pkg/messaging/lifecycle"
2930
"github.com/status-im/status-go/pkg/messaging/types"
3031
community_token "github.com/status-im/status-go/protocol/communities/token"
3132
"github.com/status-im/status-go/protocol/protobuf"
@@ -519,6 +520,33 @@ func (s *ManagerSuite) TestStartHistoryArchiveTasksInterval() {
519520
s.Require().Equal(count, 0)
520521
}
521522

523+
func (s *ManagerSuite) TestStartHistoryArchiveTasksInterval_RespectsPausedLifecycle() {
524+
err := s.archiveManager.StartTorrentClient()
525+
s.Require().NoError(err)
526+
defer s.archiveManager.Stop() //nolint: errcheck
527+
528+
messaginglifecycle.SetPausedBackground(true)
529+
defer messaginglifecycle.SetPausedBackground(false)
530+
531+
community, _, err := s.buildCommunityWithChat()
532+
s.Require().NoError(err)
533+
534+
interval := 300 * time.Millisecond
535+
go s.archiveManager.StartHistoryArchiveTasksInterval(community, interval)
536+
537+
time.Sleep(200 * time.Millisecond)
538+
s.Require().Equal(1, s.getHistoryTasksCount())
539+
540+
// Unpause to exercise lifecycle transition handling in the running loop.
541+
messaginglifecycle.SetPausedBackground(false)
542+
time.Sleep(200 * time.Millisecond)
543+
s.Require().Equal(1, s.getHistoryTasksCount())
544+
545+
s.archiveManager.StopHistoryArchiveTasksInterval(community.ID())
546+
s.archiveManager.historyArchiveTasksWaitGroup.Wait()
547+
s.Require().Equal(0, s.getHistoryTasksCount())
548+
}
549+
522550
func (s *ManagerSuite) TestStopHistoryArchiveTasksIntervals() {
523551
err := s.archiveManager.StartTorrentClient()
524552
s.Require().NoError(err)

protocol/local_notifications_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,66 @@ func TestApplyMessagePreview(t *testing.T) {
7676
})
7777
}
7878

79+
func TestGetMessagePreviewTextNilMessage(t *testing.T) {
80+
require.Equal(t, "", getMessagePreviewText(nil, nil))
81+
}
82+
83+
func TestGetMessagePreviewTextTrimsMessageText(t *testing.T) {
84+
msg := &common.Message{
85+
ChatMessage: &protobuf.ChatMessage{
86+
Text: " hello world ",
87+
},
88+
}
89+
require.Equal(t, "hello world", getMessagePreviewText(msg, nil))
90+
}
91+
92+
func TestTimestampOrZero(t *testing.T) {
93+
require.Equal(t, uint64(0), timestampOrZero(nil))
94+
95+
msg := &common.Message{WhisperTimestamp: 42}
96+
require.Equal(t, uint64(42), timestampOrZero(msg))
97+
}
98+
99+
func TestToContactRequestNotificationFallbackMessageAndZeroTimestamp(t *testing.T) {
100+
body := NotificationBody{
101+
Contact: &contacts.Contact{
102+
ID: "0xabc",
103+
DisplayName: "Alice",
104+
},
105+
Chat: &Chat{
106+
ID: "chat-1",
107+
ChatType: ChatTypeOneToOne,
108+
},
109+
}
110+
111+
notif, err := body.toContactRequestNotification("0x1", 0, messagePreviewNameAndMessage)
112+
require.NoError(t, err)
113+
require.Equal(t, "Alice sent you a contact request", notif.Message)
114+
require.Equal(t, uint64(0), notif.Timestamp)
115+
}
116+
117+
func TestToPrivateGroupInviteNotificationUsesMessagePreviewWhenPresent(t *testing.T) {
118+
body := NotificationBody{
119+
Contact: &contacts.Contact{
120+
ID: "0xabc",
121+
DisplayName: "Alice",
122+
},
123+
Chat: &Chat{
124+
ID: "group-1",
125+
Name: "Secret Group",
126+
ChatType: ChatTypePrivateGroupChat,
127+
},
128+
Message: &common.Message{
129+
ChatMessage: &protobuf.ChatMessage{
130+
Text: " please join us ",
131+
},
132+
},
133+
}
134+
135+
notif := body.toPrivateGroupInviteNotification("0x2", 0, messagePreviewNameAndMessage)
136+
require.Equal(t, "please join us", notif.Message)
137+
}
138+
79139
func TestShowMessageNotification_OneToOneChats(t *testing.T) {
80140
key, _ := crypto.GenerateKey()
81141
pkHex := types.EncodeHex(crypto.FromECDSAPub(&key.PublicKey))

server/lifecycle_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package server
2+
3+
import (
4+
"net/netip"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/require"
9+
"go.uber.org/zap"
10+
)
11+
12+
func TestGetBindAddrPortUsesCachedPortForEphemeralConfig(t *testing.T) {
13+
s := NewServer(zap.NewNop(), &Config{
14+
AddrPort: netip.MustParseAddrPort("127.0.0.1:0"),
15+
})
16+
s.cachedPort = 45678
17+
18+
require.Equal(t, netip.MustParseAddrPort("127.0.0.1:45678"), s.getBindAddrPort())
19+
}
20+
21+
func TestServerToBackgroundToForegroundReusesEphemeralPort(t *testing.T) {
22+
s := NewServer(zap.NewNop(), &Config{
23+
AddrPort: netip.MustParseAddrPort("127.0.0.1:0"),
24+
})
25+
26+
require.NoError(t, s.Start())
27+
require.Eventually(t, func() bool {
28+
return s.GetPort() != 0
29+
}, time.Second, 10*time.Millisecond)
30+
31+
firstPort := s.GetPort()
32+
require.NotZero(t, firstPort)
33+
34+
s.ToBackground()
35+
require.Eventually(t, func() bool {
36+
return !s.IsRunning()
37+
}, time.Second, 10*time.Millisecond)
38+
39+
s.ToForeground()
40+
require.Eventually(t, func() bool {
41+
return s.IsRunning() && s.GetPort() != 0
42+
}, time.Second, 10*time.Millisecond)
43+
44+
require.Equal(t, firstPort, s.GetPort())
45+
require.NoError(t, s.Stop())
46+
}

server/pairing/connection_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func (s *ConnectionParamsSuite) SetupSuite() {
7373
s.Require().NoError(err)
7474

7575
s.server = &BaseServer{
76-
Server: bs,
76+
Server: &bs,
7777
config: sc,
7878
}
7979
}

server/pairing/server.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import (
2929
*/
3030

3131
type BaseServer struct {
32-
server.Server
32+
*server.Server
3333
challengeGiver *ChallengeGiver
3434

3535
config ServerConfig
@@ -47,14 +47,15 @@ func NewBaseServer(logger *zap.Logger, e *PayloadEncryptor, config *ServerConfig
4747
return nil, errorspkg.New("invalid listen IP")
4848
}
4949

50+
srv := server.NewServer(
51+
logger,
52+
&server.Config{
53+
Cert: config.Cert,
54+
AddrPort: netip.AddrPortFrom(addr, 0),
55+
},
56+
)
5057
bs := &BaseServer{
51-
Server: server.NewServer(
52-
logger,
53-
&server.Config{
54-
Cert: config.Cert,
55-
AddrPort: netip.AddrPortFrom(addr, 0),
56-
},
57-
),
58+
Server: &srv,
5859
challengeGiver: cg,
5960
config: *config,
6061
}

server/server.go

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"net/url"
1111
"strconv"
1212
"sync"
13+
"syscall"
14+
"time"
1315

1416
"go.uber.org/zap"
1517

@@ -153,15 +155,22 @@ func (s *Server) listen() error {
153155
return nil
154156
}
155157

156-
func (s *Server) serve() {
158+
func (s *Server) serve(currentServer *http.Server, currentListener net.Listener) {
157159
defer common.LogOnPanic()
158160

159161
defer func() {
160-
s.isRunning = false
161-
s.address = nil
162+
s.lifecycleMu.Lock()
163+
defer s.lifecycleMu.Unlock()
164+
165+
// If a newer Start() already replaced server/listener, do not clobber
166+
// the latest running instance state.
167+
if s.server == currentServer && s.listener == currentListener {
168+
s.isRunning = false
169+
s.address = nil
170+
}
162171
}()
163172

164-
err := s.server.Serve(s.listener)
173+
err := currentServer.Serve(currentListener)
165174
if errors.Is(err, http.ErrServerClosed) {
166175
return
167176
}
@@ -209,23 +218,26 @@ func (s *Server) Start() error {
209218
// Mark running synchronously to avoid pause/play races where ToBackground
210219
// can run before serve() goroutine has a chance to set the state.
211220
s.isRunning = true
212-
go s.serve()
221+
go s.serve(s.server, s.listener)
213222
return nil
214223
}
215224

216225
func (s *Server) Stop() error {
217226
s.lifecycleMu.Lock()
218-
defer s.lifecycleMu.Unlock()
219-
220227
s.StopTimeout()
221228
if !s.isRunning || s.server == nil {
229+
s.lifecycleMu.Unlock()
222230
return nil
223231
}
224232

225-
// Flip state before shutdown so rapid foreground/background transitions
226-
// don't attempt a concurrent second bind to a cached port.
233+
// Capture the current instance and release the lock before Shutdown.
234+
// Shutdown waits for Serve() to return, and Serve() may update state in
235+
// its defer path under the same mutex.
236+
currentServer := s.server
227237
s.isRunning = false
228-
return s.server.Shutdown(context.Background())
238+
s.lifecycleMu.Unlock()
239+
240+
return currentServer.Shutdown(context.Background())
229241
}
230242

231243
func (s *Server) IsRunning() bool {
@@ -234,9 +246,26 @@ func (s *Server) IsRunning() bool {
234246

235247
func (s *Server) ToForeground() {
236248
err := s.Start()
237-
if err != nil {
238-
s.logger.Error("server start failed during foreground transition", zap.Error(err))
249+
if err == nil {
250+
return
251+
}
252+
253+
// On rapid pause/resume cycles with ephemeral ports, the previous listener
254+
// close can lag briefly and return EADDRINUSE for the cached port.
255+
// Retry a few times to preserve stable URL reuse semantics.
256+
if s.config != nil && s.config.AddrPort.Port() == 0 && s.cachedPort != 0 && errors.Is(err, syscall.EADDRINUSE) {
257+
for i := 0; i < 10; i++ {
258+
time.Sleep(20 * time.Millisecond)
259+
err = s.Start()
260+
if err == nil {
261+
return
262+
}
263+
if !errors.Is(err, syscall.EADDRINUSE) {
264+
break
265+
}
266+
}
239267
}
268+
s.logger.Error("server start failed during foreground transition", zap.Error(err))
240269
}
241270

242271
func (s *Server) ToBackground() {

services/ext/service_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package ext
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
8+
messaginglifecycle "github.com/status-im/status-go/pkg/messaging/lifecycle"
9+
"github.com/status-im/status-go/protocol"
10+
)
11+
12+
func TestServicePauseResumeBackgroundWithNilMessenger(t *testing.T) {
13+
svc := &Service{}
14+
require.NoError(t, svc.PauseBackground())
15+
require.NoError(t, svc.ResumeForeground())
16+
}
17+
18+
func TestServicePauseResumeBackgroundUpdatesLifecycleState(t *testing.T) {
19+
messaginglifecycle.SetPausedBackground(false)
20+
defer messaginglifecycle.SetPausedBackground(false)
21+
22+
svc := &Service{
23+
messenger: &protocol.Messenger{},
24+
}
25+
26+
require.NoError(t, svc.PauseBackground())
27+
require.True(t, messaginglifecycle.IsPausedBackground())
28+
29+
require.NoError(t, svc.ResumeForeground())
30+
require.False(t, messaginglifecycle.IsPausedBackground())
31+
}

services/newsfeed/service_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,17 @@ func (s *MessengerNewsFeedSuite) TestToggleSettings() {
120120
s.service.newsFeedManager.StopPolling()
121121
s.Require().False(s.service.newsFeedManager.IsPolling())
122122
}
123+
124+
func (s *MessengerNewsFeedSuite) TestPauseResumeBackground() {
125+
err := s.service.Start()
126+
s.Require().NoError(err)
127+
s.Require().True(s.service.started)
128+
129+
err = s.service.PauseBackground()
130+
s.Require().NoError(err)
131+
s.Require().False(s.service.started)
132+
133+
err = s.service.ResumeForeground()
134+
s.Require().NoError(err)
135+
s.Require().True(s.service.started)
136+
}

0 commit comments

Comments
 (0)