Skip to content

Commit 9fd1be5

Browse files
committed
test(client): add comprehensive tests for client messaging functionality
1 parent 100618d commit 9fd1be5

File tree

1 file changed

+316
-0
lines changed

1 file changed

+316
-0
lines changed

client_edge_cases_test.go

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
package p2p
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
func TestClientTwoWayMessaging(t *testing.T) {
13+
// Create two clients
14+
privKey1, err := GeneratePrivateKey()
15+
require.NoError(t, err)
16+
17+
privKey2, err := GeneratePrivateKey()
18+
require.NoError(t, err)
19+
20+
config1 := Config{
21+
Name: "sender",
22+
PrivateKey: privKey1,
23+
}
24+
25+
config2 := Config{
26+
Name: "receiver",
27+
PrivateKey: privKey2,
28+
}
29+
30+
cl1, err := NewClient(config1)
31+
require.NoError(t, err)
32+
defer func() {
33+
closeErr := cl1.Close()
34+
require.NoError(t, closeErr)
35+
}()
36+
37+
cl2, err := NewClient(config2)
38+
require.NoError(t, err)
39+
defer func() {
40+
closeErr := cl2.Close()
41+
require.NoError(t, closeErr)
42+
}()
43+
44+
// Subscribe to same topic
45+
msgChan1 := cl1.Subscribe("messaging-test")
46+
msgChan2 := cl2.Subscribe("messaging-test")
47+
48+
// Give time for subscriptions and discovery
49+
time.Sleep(3 * time.Second)
50+
51+
// Publish from client1
52+
ctx := context.Background()
53+
testData := []byte("Hello from client1")
54+
err = cl1.Publish(ctx, "messaging-test", testData)
55+
require.NoError(t, err)
56+
57+
// Try to receive on both clients (one should get it, the sender filters itself)
58+
select {
59+
case msg := <-msgChan2:
60+
assert.Equal(t, "sender", msg.From)
61+
assert.Equal(t, testData, msg.Data)
62+
case <-msgChan1:
63+
// Sender shouldn't receive own message
64+
t.Fatal("sender received own message")
65+
case <-time.After(5 * time.Second):
66+
// Timeout is acceptable if peers haven't discovered each other
67+
t.Log("No message received (peers may not have connected)")
68+
}
69+
}
70+
71+
func TestClientSubscribeToMultipleTopicsSequentially(t *testing.T) {
72+
privKey, err := GeneratePrivateKey()
73+
require.NoError(t, err)
74+
75+
config := Config{
76+
Name: testPeerName,
77+
PrivateKey: privKey,
78+
}
79+
80+
cl, err := NewClient(config)
81+
require.NoError(t, err)
82+
defer func() {
83+
closeErr := cl.Close()
84+
require.NoError(t, closeErr)
85+
}()
86+
87+
// Subscribe to multiple topics one at a time
88+
topics := []string{"topic-a", "topic-b", "topic-c", "topic-d", "topic-e"}
89+
channels := make([]<-chan Message, len(topics))
90+
91+
for i, topic := range topics {
92+
channels[i] = cl.Subscribe(topic)
93+
require.NotNil(t, channels[i])
94+
time.Sleep(50 * time.Millisecond) // Space out subscriptions
95+
}
96+
97+
// Verify all channels are still open
98+
for i, ch := range channels {
99+
select {
100+
case _, ok := <-ch:
101+
if !ok {
102+
t.Fatalf("channel %d closed unexpectedly", i)
103+
}
104+
case <-time.After(50 * time.Millisecond):
105+
// Expected - channel should be open but no messages
106+
}
107+
}
108+
}
109+
110+
func TestClientPublishAfterSubscribe(t *testing.T) {
111+
privKey, err := GeneratePrivateKey()
112+
require.NoError(t, err)
113+
114+
config := Config{
115+
Name: testPeerName,
116+
PrivateKey: privKey,
117+
}
118+
119+
cl, err := NewClient(config)
120+
require.NoError(t, err)
121+
defer func() {
122+
closeErr := cl.Close()
123+
require.NoError(t, closeErr)
124+
}()
125+
126+
// Subscribe first
127+
_ = cl.Subscribe("ordered-topic")
128+
time.Sleep(100 * time.Millisecond)
129+
130+
// Then publish
131+
ctx := context.Background()
132+
err = cl.Publish(ctx, "ordered-topic", []byte("data"))
133+
require.NoError(t, err)
134+
}
135+
136+
func TestClientMultiplePublishesToSameTopic(t *testing.T) {
137+
privKey, err := GeneratePrivateKey()
138+
require.NoError(t, err)
139+
140+
config := Config{
141+
Name: testPeerName,
142+
PrivateKey: privKey,
143+
}
144+
145+
cl, err := NewClient(config)
146+
require.NoError(t, err)
147+
defer func() {
148+
closeErr := cl.Close()
149+
require.NoError(t, closeErr)
150+
}()
151+
152+
ctx := context.Background()
153+
154+
// Publish multiple times to same topic
155+
for i := 0; i < 5; i++ {
156+
err := cl.Publish(ctx, "repeated-topic", []byte("message"))
157+
require.NoError(t, err)
158+
time.Sleep(10 * time.Millisecond)
159+
}
160+
}
161+
162+
func TestClientGetIDConsistencyAcrossCalls(t *testing.T) {
163+
privKey, err := GeneratePrivateKey()
164+
require.NoError(t, err)
165+
166+
config := Config{
167+
Name: testPeerName,
168+
PrivateKey: privKey,
169+
}
170+
171+
cl, err := NewClient(config)
172+
require.NoError(t, err)
173+
defer func() {
174+
closeErr := cl.Close()
175+
require.NoError(t, closeErr)
176+
}()
177+
178+
// Get ID multiple times
179+
id1 := cl.GetID()
180+
id2 := cl.GetID()
181+
id3 := cl.GetID()
182+
183+
// Should always be the same
184+
assert.Equal(t, id1, id2)
185+
assert.Equal(t, id2, id3)
186+
assert.NotEmpty(t, id1)
187+
}
188+
189+
func TestPeerTrackerEdgeCases(t *testing.T) {
190+
tracker := newPeerTracker()
191+
192+
// Test with same peer multiple times
193+
peerID := generateTestPeerID(t)
194+
195+
tracker.updateName(peerID, "name1")
196+
tracker.updateName(peerID, "name2")
197+
tracker.updateName(peerID, "name3")
198+
199+
name := tracker.getName(peerID)
200+
assert.Equal(t, "name3", name)
201+
202+
// Record message multiple times
203+
tracker.recordMessageFrom(peerID)
204+
firstTime := tracker.lastSeen[peerID]
205+
206+
time.Sleep(10 * time.Millisecond)
207+
208+
tracker.recordMessageFrom(peerID)
209+
secondTime := tracker.lastSeen[peerID]
210+
211+
assert.True(t, secondTime.After(firstTime))
212+
213+
// Get peers
214+
peers := tracker.getAllTopicPeers()
215+
assert.Len(t, peers, 1)
216+
}
217+
218+
func TestClientCloseWithActiveSubscriptions(t *testing.T) {
219+
privKey, err := GeneratePrivateKey()
220+
require.NoError(t, err)
221+
222+
config := Config{
223+
Name: testPeerName,
224+
PrivateKey: privKey,
225+
}
226+
227+
cl, err := NewClient(config)
228+
require.NoError(t, err)
229+
230+
// Create many active subscriptions
231+
channels := make([]<-chan Message, 10)
232+
for i := 0; i < 10; i++ {
233+
channels[i] = cl.Subscribe("topic-" + string(rune('0'+i)))
234+
}
235+
236+
// Give subscriptions time to start
237+
time.Sleep(200 * time.Millisecond)
238+
239+
// Close with active subscriptions
240+
err = cl.Close()
241+
require.NoError(t, err)
242+
243+
// All channels should be closed
244+
for i, ch := range channels {
245+
select {
246+
case _, ok := <-ch:
247+
assert.False(t, ok, "channel %d should be closed", i)
248+
case <-time.After(100 * time.Millisecond):
249+
t.Fatalf("channel %d not closed in time", i)
250+
}
251+
}
252+
}
253+
254+
func TestClientPublishToMultipleTopics(t *testing.T) {
255+
privKey, err := GeneratePrivateKey()
256+
require.NoError(t, err)
257+
258+
config := Config{
259+
Name: testPeerName,
260+
PrivateKey: privKey,
261+
}
262+
263+
cl, err := NewClient(config)
264+
require.NoError(t, err)
265+
defer func() {
266+
closeErr := cl.Close()
267+
require.NoError(t, closeErr)
268+
}()
269+
270+
ctx := context.Background()
271+
272+
// Publish to different topics
273+
topics := []string{"t1", "t2", "t3", "t4", "t5"}
274+
for _, topic := range topics {
275+
err := cl.Publish(ctx, topic, []byte("test"))
276+
require.NoError(t, err)
277+
}
278+
}
279+
280+
func TestNewClientQuickStartAndStop(t *testing.T) {
281+
// Test rapid client creation and shutdown
282+
for i := 0; i < 3; i++ {
283+
privKey, err := GeneratePrivateKey()
284+
require.NoError(t, err)
285+
286+
config := Config{
287+
Name: "quick-" + string(rune('0'+i)),
288+
PrivateKey: privKey,
289+
}
290+
291+
cl, err := NewClient(config)
292+
require.NoError(t, err)
293+
294+
// Immediately close
295+
err = cl.Close()
296+
require.NoError(t, err)
297+
}
298+
}
299+
300+
func TestEvictStalePeersEdgeCaseTiny(t *testing.T) {
301+
logger := &DefaultLogger{}
302+
now := time.Now()
303+
304+
peers := []cachedPeer{
305+
{
306+
ID: "peer1",
307+
LastSeen: now.Add(-10 * time.Second),
308+
},
309+
}
310+
311+
// Very small TTL
312+
result := evictStalePeers(peers, 5*time.Second, logger)
313+
314+
// Peer should be evicted (older than 5 seconds)
315+
assert.Empty(t, result)
316+
}

0 commit comments

Comments
 (0)