@@ -3,7 +3,12 @@ package lib
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "net"
7
+ "net/http"
8
+ "net/http/httptest"
6
9
"testing"
10
+
11
+ "github.com/qri-io/qri/api/util"
7
12
)
8
13
9
14
func TestRegisterMethods (t * testing.T ) {
@@ -71,6 +76,123 @@ func TestRegisterVariadicReturn(t *testing.T) {
71
76
}
72
77
}
73
78
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
+
74
196
func expectToPanic (t * testing.T , regFunc func (), expectMessage string ) {
75
197
t .Helper ()
76
198
@@ -213,6 +335,19 @@ func (m *fruitMethods) Banana(ctx context.Context, p *fruitParams) (string, Curs
213
335
return "" , nil , dispatchReturnError (got , err )
214
336
}
215
337
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
+
216
351
// Implementation for fruit
217
352
type fruitImpl struct {}
218
353
@@ -224,3 +359,12 @@ func (fruitImpl) Banana(scp scope, p *fruitParams) (string, Cursor, error) {
224
359
var cur Cursor
225
360
return "batman" , cur , fmt .Errorf ("success" )
226
361
}
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