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  }