@@ -3,7 +3,12 @@ package lib
33import (
44 "context"
55 "fmt"
6+ "net"
7+ "net/http"
8+ "net/http/httptest"
69 "testing"
10+
11+ "github.com/qri-io/qri/api/util"
712)
813
914func TestRegisterMethods (t * testing.T ) {
@@ -71,6 +76,123 @@ func TestRegisterVariadicReturn(t *testing.T) {
7176 }
7277}
7378
79+ func TestVariadicReturnsWorkOverHTTP (t * testing.T ) {
80+ ctx := context .Background ()
81+
82+ // Instance that registers the fruit methods
83+ servInst , servCleanup := NewMemTestInstance (ctx , t )
84+ defer servCleanup ()
85+ servFruit := & fruitMethods {d : servInst }
86+ reg := make (map [string ]callable )
87+ servInst .registerOne ("fruit" , servFruit , fruitImpl {}, reg )
88+ servInst .regMethods = & regMethodSet {reg : reg }
89+
90+ // A local call, no RPC used
91+ err := servFruit .Apple (ctx , & fruitParams {})
92+ expectErr := "no more apples"
93+ if err .Error () != expectErr {
94+ t .Errorf ("error mismatch, expect: %s, got: %s" , expectErr , err )
95+ }
96+
97+ // Instance that acts as a client of another
98+ clientInst , clientCleanup := NewMemTestInstance (ctx , t )
99+ defer clientCleanup ()
100+ clientFruit := & fruitMethods {d : clientInst }
101+ reg = make (map [string ]callable )
102+ clientInst .registerOne ("fruit" , clientFruit , fruitImpl {}, reg )
103+ clientInst .regMethods = & regMethodSet {reg : reg }
104+
105+ // Run the first instance in "connect" mode, tell the second
106+ // instance to use it for RPC calls
107+ httpClient , connectCleanup := serverConnectAndListen (t , servInst , 7890 )
108+ defer connectCleanup ()
109+ clientInst .http = httpClient
110+
111+ // Call the method, which will be send over RPC
112+ err = clientFruit .Apple (ctx , & fruitParams {})
113+ if err == nil {
114+ t .Fatal ("expected to get error but did not get one" )
115+ }
116+ expectErr = newHTTPResponseError ("no more apples" )
117+ if err .Error () != expectErr {
118+ t .Errorf ("error mismatch, expect: %s, got: %s" , expectErr , err )
119+ }
120+
121+ // Call another method
122+ _ , _ , err = clientFruit .Banana (ctx , & fruitParams {})
123+ if err == nil {
124+ t .Fatal ("expected to get error but did not get one" )
125+ }
126+ expectErr = newHTTPResponseError ("success" )
127+ if err .Error () != expectErr {
128+ t .Errorf ("error mismatch, expect: %s, got: %s" , expectErr , err )
129+ }
130+
131+ // Call another method, which won't return an error
132+ err = clientFruit .Cherry (ctx , & fruitParams {})
133+ if err != nil {
134+ t .Errorf ("%s" , err )
135+ }
136+
137+ // Call the last method
138+ val , _ , err := clientFruit .Date (ctx , & fruitParams {})
139+ if err != nil {
140+ t .Errorf ("%s" , err )
141+ }
142+ if val != "January 1st" {
143+ t .Errorf ("value mismatch, expect: January 1st, got: %s" , val )
144+ }
145+ }
146+
147+ func serverConnectAndListen (t * testing.T , servInst * Instance , port int ) (* HTTPClient , func ()) {
148+ address := fmt .Sprintf ("/ip4/127.0.0.1/tcp/%d" , port )
149+ connection , err := NewHTTPClient (address )
150+ if err != nil {
151+ t .Fatal (err )
152+ }
153+
154+ handler := func (w http.ResponseWriter , r * http.Request ) {
155+ method := ""
156+ if r .URL .Path == "/apple/" {
157+ method = "fruit.apple"
158+ } else if r .URL .Path == "/banana/" {
159+ method = "fruit.banana"
160+ } else if r .URL .Path == "/cherry/" {
161+ method = "fruit.cherry"
162+ } else if r .URL .Path == "/date/" {
163+ method = "fruit.date"
164+ }
165+ p := servInst .NewInputParam (method )
166+ res , _ , err := servInst .Dispatch (r .Context (), method , p )
167+ if err != nil {
168+ util .RespondWithError (w , err )
169+ return
170+ }
171+ util .WriteResponse (w , res )
172+ }
173+ mockAPIServer := httptest .NewUnstartedServer (http .HandlerFunc (handler ))
174+ listener , err := net .Listen ("tcp" , fmt .Sprintf (":%d" , port ))
175+ if err != nil {
176+ t .Fatal (err .Error ())
177+ }
178+ mockAPIServer .Listener = listener
179+ mockAPIServer .Start ()
180+ apiServerCleanup := func () {
181+ mockAPIServer .Close ()
182+ }
183+ return connection , apiServerCleanup
184+ }
185+
186+ func newHTTPResponseError (msg string ) string {
187+ tmpl := `{
188+ "meta": {
189+ "code": 500,
190+ "error": "%s"
191+ }
192+ }`
193+ return fmt .Sprintf (tmpl , msg )
194+ }
195+
74196func expectToPanic (t * testing.T , regFunc func (), expectMessage string ) {
75197 t .Helper ()
76198
@@ -213,6 +335,19 @@ func (m *fruitMethods) Banana(ctx context.Context, p *fruitParams) (string, Curs
213335 return "" , nil , dispatchReturnError (got , err )
214336}
215337
338+ func (m * fruitMethods ) Cherry (ctx context.Context , p * fruitParams ) error {
339+ _ , _ , err := m .d .Dispatch (ctx , dispatchMethodName (m , "cherry" ), p )
340+ return err
341+ }
342+
343+ func (m * fruitMethods ) Date (ctx context.Context , p * fruitParams ) (string , Cursor , error ) {
344+ got , cur , err := m .d .Dispatch (ctx , dispatchMethodName (m , "date" ), p )
345+ if res , ok := got .(string ); ok {
346+ return res , cur , err
347+ }
348+ return "" , nil , dispatchReturnError (got , err )
349+ }
350+
216351// Implementation for fruit
217352type fruitImpl struct {}
218353
@@ -224,3 +359,12 @@ func (fruitImpl) Banana(scp scope, p *fruitParams) (string, Cursor, error) {
224359 var cur Cursor
225360 return "batman" , cur , fmt .Errorf ("success" )
226361}
362+
363+ func (fruitImpl ) Cherry (scp scope , p * fruitParams ) error {
364+ return nil
365+ }
366+
367+ func (fruitImpl ) Date (scp scope , p * fruitParams ) (string , Cursor , error ) {
368+ var cur Cursor
369+ return "January 1st" , cur , nil
370+ }
0 commit comments