awareness_test.go
1package sync
2
3import (
4 "testing"
5 "time"
6)
7
8func TestNewAwarenessClient(t *testing.T) {
9 client := NewAwarenessClient(123, time.Minute, func() {})
10
11 if client == nil {
12 t.Error("expected non-nil client")
13 }
14
15 if client.clients == nil {
16 t.Error("expected clients map to be initialized")
17 }
18
19 if client.clientID != 123 {
20 t.Errorf("expected clientID 123, got %d", client.clientID)
21 }
22}
23
24func TestAwarenessStateStruct(t *testing.T) {
25 state := AwarenessState{
26 ClientID: 123,
27 Clock: 456,
28 State: map[string]interface{}{"key": "value"},
29 }
30
31 if state.ClientID != 123 {
32 t.Errorf("expected ClientID 123, got %d", state.ClientID)
33 }
34
35 if state.Clock != 456 {
36 t.Errorf("expected Clock 456, got %d", state.Clock)
37 }
38
39 if state.State["key"] != "value" {
40 t.Errorf("expected State[key] value, got %v", state.State["key"])
41 }
42}
43
44func TestAwarenessClientStates(t *testing.T) {
45 client := NewAwarenessClient(1, time.Minute, func() {})
46
47 states := client.States()
48 if len(states) != 0 {
49 t.Errorf("expected empty states, got %d", len(states))
50 }
51
52 client.mu.Lock()
53 client.clients[1] = AwarenessState{
54 ClientID: 1,
55 Clock: 10,
56 State: map[string]interface{}{"name": "alice"},
57 }
58 client.clients[2] = AwarenessState{
59 ClientID: 2,
60 Clock: 20,
61 State: map[string]interface{}{"name": "bob"},
62 }
63 client.mu.Unlock()
64
65 states = client.States()
66 if len(states) != 2 {
67 t.Errorf("expected 2 states, got %d", len(states))
68 }
69}
70
71func TestAwarenessClientLastUpdate(t *testing.T) {
72 client := NewAwarenessClient(1, time.Minute, func() {})
73
74 before := client.lastUpdate
75
76 time.Sleep(10 * time.Millisecond)
77
78 client.mu.Lock()
79 client.lastUpdate = time.Now()
80 client.mu.Unlock()
81
82 after := client.lastUpdate
83
84 if before.Equal(after) {
85 t.Error("expected lastUpdate to be updated")
86 }
87}
88
89func TestAwarenessClientTimeoutNoOtherClients(t *testing.T) {
90 disconnected := false
91 client := NewAwarenessClient(1, time.Minute, func() {
92 disconnected = true
93 })
94
95 client.mu.Lock()
96 client.clients[1] = AwarenessState{
97 ClientID: 1,
98 Clock: 10,
99 State: map[string]interface{}{"name": "self"},
100 }
101 client.mu.Unlock()
102
103 client.checkTimeout()
104
105 if disconnected {
106 t.Error("should not disconnect when only self client exists")
107 }
108}
109
110func TestAwarenessClientTimeoutWithOtherClients(t *testing.T) {
111 disconnected := false
112 client := NewAwarenessClient(1, time.Minute, func() {
113 disconnected = true
114 })
115
116 client.mu.Lock()
117 client.clients[1] = AwarenessState{
118 ClientID: 1,
119 Clock: 10,
120 State: map[string]interface{}{"name": "self"},
121 }
122 client.clients[2] = AwarenessState{
123 ClientID: 2,
124 Clock: 20,
125 State: map[string]interface{}{"name": "other"},
126 }
127 client.mu.Unlock()
128
129 client.checkTimeout()
130
131 if disconnected {
132 t.Error("should not disconnect when other clients exist")
133 }
134}
135
136func TestAwarenessHandleUpdateEmptyData(t *testing.T) {
137 client := NewAwarenessClient(1, time.Minute, func() {})
138
139 err := client.HandleUpdate([]byte{})
140 if err != nil {
141 t.Errorf("expected no error for empty data, got %v", err)
142 }
143}
144
145func TestAwarenessHandleUpdateTruncatedData(t *testing.T) {
146 client := NewAwarenessClient(1, time.Minute, func() {})
147
148 // Data with only message type (count=0), should work
149 err := client.HandleUpdate([]byte{0})
150 if err != nil {
151 t.Errorf("expected no error for zero count, got %v", err)
152 }
153
154 // Data with count=1 but no client ID
155 err = client.HandleUpdate([]byte{1})
156 if err != nil {
157 t.Errorf("expected no error for truncated data with count, got %v", err)
158 }
159}
160
161func TestAwarenessHandleUpdateWithState(t *testing.T) {
162 client := NewAwarenessClient(1, time.Minute, func() {})
163
164 // Build a valid awareness update with one client
165 stateJSON := []byte(`{"name":"alice"}`)
166 count := appendVarUint(nil, 1)
167 clientID := appendVarUint(nil, 1)
168 clock := appendVarUint(nil, 1)
169 data := append(append(append(count, clientID...), clock...), stateJSON...)
170
171 err := client.HandleUpdate(data)
172 if err != nil {
173 t.Errorf("failed to handle update: %v", err)
174 }
175
176 states := client.States()
177 if len(states) != 1 {
178 t.Errorf("expected 1 state, got %d", len(states))
179 }
180
181 if states[0].State["name"] != "alice" {
182 t.Errorf("expected state name 'alice', got %v", states[0].State["name"])
183 }
184}
185
186func TestAwarenessClientID(t *testing.T) {
187 client := NewAwarenessClient(123, time.Minute, func() {})
188
189 if client.ClientID() != 123 {
190 t.Errorf("expected clientID 123, got %d", client.ClientID())
191 }
192}
193
194func TestAwarenessHandleUpdateMultipleClients(t *testing.T) {
195 client := NewAwarenessClient(1, time.Minute, func() {})
196
197 // Note: The current implementation only processes the first client
198 // because it sets offset = len(data) after reading each state.
199 // This test verifies that at least one client is processed correctly.
200 state1 := []byte(`{"name":"alice"}`)
201
202 var data []byte
203 data = appendVarUint(data, 2) // count = 2, but only first will be read
204 data = appendVarUint(data, 1)
205 data = appendVarUint(data, 1)
206 data = append(data, state1...)
207
208 err := client.HandleUpdate(data)
209 if err != nil {
210 t.Errorf("failed to handle update: %v", err)
211 }
212
213 states := client.States()
214 if len(states) != 1 {
215 t.Errorf("expected 1 state (implementation only reads first), got %d", len(states))
216 }
217
218 if states[0].State["name"] != "alice" {
219 t.Errorf("expected state name 'alice', got %v", states[0].State["name"])
220 }
221}