github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/endpoints/sse_handler_test.go (about) 1 /* 2 * Copyright (C) 2020 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package endpoints 19 20 import ( 21 "bufio" 22 "context" 23 "fmt" 24 "math/big" 25 "net" 26 "net/http" 27 "strings" 28 "testing" 29 "time" 30 31 "github.com/ethereum/go-ethereum/common" 32 "github.com/gin-gonic/gin" 33 "github.com/mysteriumnetwork/node/identity" 34 "github.com/stretchr/testify/assert" 35 36 "github.com/mysteriumnetwork/node/core/connection/connectionstate" 37 nodeEvent "github.com/mysteriumnetwork/node/core/node/event" 38 stateEvent "github.com/mysteriumnetwork/node/core/state/event" 39 "github.com/mysteriumnetwork/node/identity/registry" 40 "github.com/mysteriumnetwork/node/session/pingpong/event" 41 ) 42 43 type mockStateProvider struct { 44 stateToReturn stateEvent.State 45 } 46 47 func (msp *mockStateProvider) GetState() stateEvent.State { 48 return msp.stateToReturn 49 } 50 51 func (msp *mockStateProvider) GetConnection(id string) stateEvent.Connection { 52 return msp.stateToReturn.Connections["1"] 53 } 54 55 func TestHandler_Stops(t *testing.T) { 56 h := NewSSEHandler(&mockStateProvider{}) 57 58 wait := make(chan struct{}) 59 go func() { 60 h.serve() 61 wait <- struct{}{} 62 }() 63 64 h.stop() 65 <-wait 66 } 67 68 func TestHandler_ConsumeNodeEvent_Stops(t *testing.T) { 69 h := NewSSEHandler(&mockStateProvider{}) 70 me := nodeEvent.Payload{ 71 Status: nodeEvent.StatusStopped, 72 } 73 h.ConsumeNodeEvent(me) 74 h.serve() 75 } 76 77 func TestHandler_ConsumeNodeEvent_Starts(t *testing.T) { 78 h := NewSSEHandler(&mockStateProvider{}) 79 me := nodeEvent.Payload{ 80 Status: nodeEvent.StatusStarted, 81 } 82 83 h.ConsumeNodeEvent(me) 84 85 // without starting, this would block forever 86 h.newClients <- make(chan string) 87 h.newClients <- make(chan string) 88 89 h.stop() 90 } 91 92 func TestHandler_SendsInitialAndFollowingStates(t *testing.T) { 93 msp := &mockStateProvider{stateToReturn: stateEvent.State{Connections: make(map[string]stateEvent.Connection)}} 94 h := NewSSEHandler(msp) 95 go h.serve() 96 defer h.stop() 97 laddr := net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0} 98 listener, err := net.ListenTCP("tcp4", &laddr) 99 assert.Nil(t, err) 100 addr := listener.Addr() 101 port := addr.(*net.TCPAddr).Port 102 defer listener.Close() 103 104 router := gin.Default() 105 router.GET("/whatever", h.Sub) 106 serveExit := make(chan error) 107 go func() { 108 err = http.Serve(listener, router) 109 serveExit <- err 110 }() 111 112 time.Sleep(time.Millisecond * 50) 113 w := fmt.Sprintf("http://127.0.0.1:%v/whatever", port) 114 req, _ := http.NewRequest("GET", w, nil) 115 ctx, cancel := context.WithCancel(context.Background()) 116 req = req.WithContext(ctx) 117 c := http.Client{} 118 resp, err := c.Do(req) 119 assert.Nil(t, err) 120 results := make(chan string) 121 go func() { 122 reader := bufio.NewReader(resp.Body) 123 for { 124 line, err := reader.ReadBytes('\n') 125 if err != nil { 126 return 127 } 128 stringified := strings.Join(strings.Fields(strings.TrimSpace(string(line))), " ") 129 if len(stringified) > 0 { 130 results <- stringified 131 } 132 } 133 }() 134 135 msg := <-results 136 assert.Regexp(t, "^data:\\s?{.*}$", msg) 137 msgJSON := strings.TrimPrefix(msg, "data: ") 138 expectJSON := ` 139 { 140 "payload": { 141 "service_info": null, 142 "sessions": [], 143 "sessions_stats": { 144 "count": 0, 145 "count_consumers": 0, 146 "sum_bytes_received": 0, 147 "sum_bytes_sent": 0, 148 "sum_duration": 0, 149 "sum_tokens": 0 150 }, 151 "consumer": { 152 "connection": { 153 "status": "NotConnected" 154 } 155 }, 156 "identities": [], 157 "channels": [] 158 }, 159 "type": "state-change" 160 }` 161 assert.JSONEq(t, expectJSON, msgJSON) 162 163 changedState := msp.GetState() 164 h.ConsumeStateEvent(changedState) 165 166 msg = <-results 167 assert.Regexp(t, "^data:\\s?{.*}$", msg) 168 msgJSON = strings.TrimPrefix(msg, "data: ") 169 expectJSON = ` 170 { 171 "payload": { 172 "service_info": null, 173 "sessions": [], 174 "sessions_stats": { 175 "count": 0, 176 "count_consumers": 0, 177 "sum_bytes_received": 0, 178 "sum_bytes_sent": 0, 179 "sum_duration": 0, 180 "sum_tokens": 0 181 }, 182 "consumer": { 183 "connection": { 184 "status": "NotConnected" 185 } 186 }, 187 "identities": [], 188 "channels": [] 189 }, 190 "type": "state-change" 191 }` 192 assert.JSONEq(t, expectJSON, msgJSON) 193 194 msp.stateToReturn.Connections["1"] = stateEvent.Connection{ 195 Session: connectionstate.Status{State: connectionstate.Connecting, SessionID: "1", ConsumerID: identity.Identity{Address: "0x123"}}, 196 Statistics: connectionstate.Statistics{BytesSent: 1, BytesReceived: 2}, 197 } 198 changedState = msp.GetState() 199 changedState.Identities = []stateEvent.Identity{ 200 { 201 Address: "0xd535eba31e9bd2d7a4e34852e6292b359e5c77f7", 202 RegistrationStatus: registry.Registered, 203 ChannelAddress: common.HexToAddress("0x000000000000000000000000000000000000000a"), 204 Balance: big.NewInt(50), 205 Earnings: big.NewInt(1), 206 EarningsTotal: big.NewInt(100), 207 EarningsPerHermes: map[common.Address]event.Earnings{ 208 common.HexToAddress("0x200000000000000000000000000000000000000a"): { 209 LifetimeBalance: big.NewInt(100), 210 UnsettledBalance: big.NewInt(50), 211 }, 212 }, 213 }, 214 } 215 h.ConsumeStateEvent(changedState) 216 217 msg = <-results 218 assert.Regexp(t, "^data:\\s?{.*}$", msg) 219 msgJSON = strings.TrimPrefix(msg, "data: ") 220 expectJSON = ` 221 { 222 "payload": { 223 "service_info": null, 224 "sessions": [], 225 "sessions_stats": { 226 "count": 0, 227 "count_consumers": 0, 228 "sum_bytes_received": 0, 229 "sum_bytes_sent": 0, 230 "sum_duration": 0, 231 "sum_tokens": 0 232 }, 233 "consumer": { 234 "connection": { 235 "consumer_id":"0x123", 236 "session_id": "1", 237 "status": "Connecting" 238 } 239 }, 240 "identities": [ 241 { 242 "id": "0xd535eba31e9bd2d7a4e34852e6292b359e5c77f7", 243 "registration_status": "Registered", 244 "channel_address": "0x000000000000000000000000000000000000000A", 245 "balance": 50, 246 "earnings": 1, 247 "earnings_total": 100, 248 "balance_tokens": { 249 "wei": "50", 250 "ether": "0.00000000000000005", 251 "human": "0" 252 }, 253 "earnings_tokens": { 254 "wei": "1", 255 "ether": "0.000000000000000001", 256 "human": "0" 257 }, 258 "earnings_total_tokens": { 259 "wei": "100", 260 "ether": "0.0000000000000001", 261 "human": "0" 262 }, 263 "stake": 0, 264 "hermes_id": "0x0000000000000000000000000000000000000000", 265 "earnings_per_hermes": { 266 "0x200000000000000000000000000000000000000A": { 267 "earnings": { 268 "wei": "50", 269 "ether": "0.00000000000000005", 270 "human": "0" 271 }, 272 "earnings_total": { 273 "wei": "100", 274 "ether": "0.0000000000000001", 275 "human": "0" 276 } 277 } 278 } 279 } 280 ], 281 "channels": [] 282 }, 283 "type": "state-change" 284 }` 285 assert.JSONEq(t, expectJSON, msgJSON) 286 287 cancel() 288 listener.Close() 289 290 <-serveExit 291 }