github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/engine/access/rest/routes/test_helpers.go (about) 1 package routes 2 3 import ( 4 "bufio" 5 "bytes" 6 "fmt" 7 "io" 8 "net" 9 "net/http" 10 "net/http/httptest" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/require" 16 17 "github.com/onflow/flow-go/access" 18 "github.com/onflow/flow-go/access/mock" 19 "github.com/onflow/flow-go/engine/access/state_stream" 20 "github.com/onflow/flow-go/engine/access/state_stream/backend" 21 "github.com/onflow/flow-go/engine/access/subscription" 22 "github.com/onflow/flow-go/model/flow" 23 "github.com/onflow/flow-go/module/metrics" 24 "github.com/onflow/flow-go/utils/unittest" 25 ) 26 27 const ( 28 ExpandableFieldPayload = "payload" 29 ExpandableExecutionResult = "execution_result" 30 sealedHeightQueryParam = "sealed" 31 finalHeightQueryParam = "final" 32 startHeightQueryParam = "start_height" 33 endHeightQueryParam = "end_height" 34 heightQueryParam = "height" 35 startBlockIdQueryParam = "start_block_id" 36 eventTypesQueryParams = "event_types" 37 addressesQueryParams = "addresses" 38 contractsQueryParams = "contracts" 39 heartbeatIntervalQueryParam = "heartbeat_interval" 40 ) 41 42 // fakeNetConn implements a mocked ws connection that can be injected in testing logic. 43 type fakeNetConn struct { 44 io.Writer 45 closed chan struct{} 46 } 47 48 var _ net.Conn = (*fakeNetConn)(nil) 49 50 // Close closes the fakeNetConn and signals its closure by closing the "closed" channel. 51 func (c fakeNetConn) Close() error { 52 select { 53 case <-c.closed: 54 default: 55 close(c.closed) 56 } 57 return nil 58 } 59 60 func (c fakeNetConn) LocalAddr() net.Addr { return localAddr } 61 func (c fakeNetConn) RemoteAddr() net.Addr { return remoteAddr } 62 func (c fakeNetConn) SetDeadline(t time.Time) error { return nil } 63 func (c fakeNetConn) SetReadDeadline(t time.Time) error { return nil } 64 func (c fakeNetConn) SetWriteDeadline(t time.Time) error { return nil } 65 func (c fakeNetConn) Read(p []byte) (n int, err error) { 66 <-c.closed 67 return 0, fmt.Errorf("closed") 68 } 69 70 type fakeAddr int 71 72 var ( 73 localAddr = fakeAddr(1) 74 remoteAddr = fakeAddr(2) 75 ) 76 77 func (a fakeAddr) Network() string { 78 return "net" 79 } 80 81 func (a fakeAddr) String() string { 82 return "str" 83 } 84 85 // testHijackResponseRecorder is a custom ResponseRecorder that implements the http.Hijacker interface 86 // for testing WebSocket connections and hijacking. 87 type testHijackResponseRecorder struct { 88 *httptest.ResponseRecorder 89 closed chan struct{} 90 responseBuff *bytes.Buffer 91 } 92 93 var _ http.Hijacker = (*testHijackResponseRecorder)(nil) 94 95 // Hijack implements the http.Hijacker interface by returning a fakeNetConn and a bufio.ReadWriter 96 // that simulate a hijacked connection. 97 func (w *testHijackResponseRecorder) Hijack() (net.Conn, *bufio.ReadWriter, error) { 98 br := bufio.NewReaderSize(strings.NewReader(""), subscription.DefaultSendBufferSize) 99 bw := bufio.NewWriterSize(&bytes.Buffer{}, subscription.DefaultSendBufferSize) 100 w.responseBuff = bytes.NewBuffer(make([]byte, 0)) 101 w.closed = make(chan struct{}, 1) 102 103 return fakeNetConn{w.responseBuff, w.closed}, bufio.NewReadWriter(br, bw), nil 104 } 105 106 func (w *testHijackResponseRecorder) Close() error { 107 select { 108 case <-w.closed: 109 default: 110 close(w.closed) 111 } 112 return nil 113 } 114 115 // newTestHijackResponseRecorder creates a new instance of testHijackResponseRecorder. 116 func newTestHijackResponseRecorder() *testHijackResponseRecorder { 117 return &testHijackResponseRecorder{ 118 ResponseRecorder: httptest.NewRecorder(), 119 } 120 } 121 122 func executeRequest(req *http.Request, backend access.API) *httptest.ResponseRecorder { 123 router := NewRouterBuilder( 124 unittest.Logger(), 125 metrics.NewNoopCollector(), 126 ).AddRestRoutes( 127 backend, 128 flow.Testnet.Chain(), 129 ).Build() 130 131 rr := httptest.NewRecorder() 132 router.ServeHTTP(rr, req) 133 return rr 134 } 135 136 func executeWsRequest(req *http.Request, stateStreamApi state_stream.API, responseRecorder *testHijackResponseRecorder, chain flow.Chain) { 137 restCollector := metrics.NewNoopCollector() 138 139 config := backend.Config{ 140 EventFilterConfig: state_stream.DefaultEventFilterConfig, 141 MaxGlobalStreams: subscription.DefaultMaxGlobalStreams, 142 HeartbeatInterval: subscription.DefaultHeartbeatInterval, 143 } 144 145 router := NewRouterBuilder(unittest.Logger(), restCollector).AddWsRoutes( 146 stateStreamApi, 147 chain, config).Build() 148 router.ServeHTTP(responseRecorder, req) 149 } 150 151 func assertOKResponse(t *testing.T, req *http.Request, expectedRespBody string, backend *mock.API) { 152 assertResponse(t, req, http.StatusOK, expectedRespBody, backend) 153 } 154 155 func assertResponse(t *testing.T, req *http.Request, status int, expectedRespBody string, backend *mock.API) { 156 rr := executeRequest(req, backend) 157 actualResponseBody := rr.Body.String() 158 require.JSONEq(t, 159 expectedRespBody, 160 actualResponseBody, 161 fmt.Sprintf("Failed Request: %s\nExpected JSON:\n %s \nActual JSON:\n %s\n", req.URL, expectedRespBody, actualResponseBody), 162 ) 163 require.Equal(t, status, rr.Code) 164 }