github.com/lmittmann/w3@v0.20.0/rpctest/server.go (about) 1 // Package rpctest provides utilities for testing RPC methods. 2 package rpctest 3 4 import ( 5 "bufio" 6 "bytes" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "os" 11 "sync" 12 "testing" 13 ) 14 15 // Server is a fake RPC endpoint that responds only to a single requests that 16 // is defined in a golden-file. 17 // 18 // Request golden-files have the following format to define a single request 19 // and the corresponding response: 20 // 21 // // Comments and empty lines will be ignored. 22 // // Request starts with ">". 23 // > {"jsonrpc":"2.0","id":1,"method":"eth_chainId"} 24 // // Response starts with "<". 25 // < {"jsonrpc":"2.0","id":1,"result":"0x1"} 26 type Server struct { 27 t *testing.T 28 29 reader io.Reader 30 readOnce sync.Once 31 in []byte 32 out []byte 33 34 httptestSrv *httptest.Server 35 } 36 37 // NewServer returns a new instance of Server that serves the golden-file from 38 // Reader r. 39 func NewServer(t *testing.T, r io.Reader) *Server { 40 srv := &Server{t: t, reader: r} 41 httptestSrv := httptest.NewServer(srv) 42 43 srv.httptestSrv = httptestSrv 44 return srv 45 } 46 47 // NewFileServer returns a new instance of Server that serves the golden-file 48 // from the given filename. 49 func NewFileServer(t *testing.T, filename string) *Server { 50 f, err := os.Open(filename) 51 if err != nil { 52 t.Fatalf("Failed to open file: %v", err) 53 } 54 55 return NewServer(t, f) 56 } 57 58 func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 59 srv.readOnce.Do(srv.readGolden) 60 61 // read body 62 body, err := io.ReadAll(r.Body) 63 if err != nil { 64 srv.t.Fatalf("Failed to read body: %v", err) 65 } 66 67 // check body 68 if !bytes.Equal(srv.in, body) { 69 srv.t.Fatalf("Invalid request body (-want, +got)\n-%s\n+%s", srv.in, body) 70 } 71 72 // respond 73 w.Header().Set("Content-Type", "application/json") 74 w.Write(srv.out) 75 } 76 77 // URL returns the servers RPC endpoint url. 78 func (srv *Server) URL() string { 79 return srv.httptestSrv.URL 80 } 81 82 // Close shuts down the server. 83 func (srv *Server) Close() { 84 srv.httptestSrv.Close() 85 } 86 87 func (srv *Server) readGolden() { 88 if rc, ok := srv.reader.(io.ReadCloser); ok { 89 defer rc.Close() 90 } 91 92 scan := bufio.NewScanner(srv.reader) 93 for scan.Scan() { 94 line := scan.Bytes() 95 if len(line) <= 0 { 96 continue // skip empty lines 97 } 98 99 switch line[0] { 100 case '>': 101 trimedLine := bytes.Trim(line, "> ") 102 srv.in = make([]byte, len(trimedLine)) 103 copy(srv.in, trimedLine) 104 case '<': 105 trimedLine := bytes.Trim(line, "< ") 106 srv.out = make([]byte, len(trimedLine)) 107 copy(srv.out, trimedLine) 108 case '/': // ignore lines starting with "/" 109 default: 110 srv.t.Fatalf("Invalid line %q", scan.Text()) 111 } 112 } 113 if err := scan.Err(); err != nil { 114 srv.t.Fatalf("Failed to scan file: %v", err) 115 } 116 }