github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/rpc/jsoncodec/codec_test.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package jsoncodec_test 5 6 import ( 7 "encoding/json" 8 "errors" 9 "io" 10 "reflect" 11 stdtesting "testing" 12 13 "github.com/juju/testing" 14 jc "github.com/juju/testing/checkers" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/rpc" 18 "github.com/juju/juju/rpc/jsoncodec" 19 ) 20 21 type suite struct { 22 testing.LoggingSuite 23 } 24 25 var _ = gc.Suite(&suite{}) 26 27 func TestPackage(t *stdtesting.T) { 28 gc.TestingT(t) 29 } 30 31 type value struct { 32 X string 33 } 34 35 func (*suite) TestRead(c *gc.C) { 36 for i, test := range []struct { 37 msg string 38 expectHdr rpc.Header 39 expectBody interface{} 40 }{{ 41 msg: `{"RequestId": 1, "Type": "foo", "Id": "id", "Request": "frob", "Params": {"X": "param"}}`, 42 expectHdr: rpc.Header{ 43 RequestId: 1, 44 Request: rpc.Request{ 45 Type: "foo", 46 Id: "id", 47 Action: "frob", 48 }, 49 }, 50 expectBody: &value{X: "param"}, 51 }, { 52 msg: `{"RequestId": 2, "Error": "an error", "ErrorCode": "a code"}`, 53 expectHdr: rpc.Header{ 54 RequestId: 2, 55 Error: "an error", 56 ErrorCode: "a code", 57 }, 58 expectBody: new(map[string]interface{}), 59 }, { 60 msg: `{"RequestId": 3, "Response": {"X": "result"}}`, 61 expectHdr: rpc.Header{ 62 RequestId: 3, 63 }, 64 expectBody: &value{X: "result"}, 65 }, { 66 msg: `{"RequestId": 4, "Type": "foo", "Version": 2, "Id": "id", "Request": "frob", "Params": {"X": "param"}}`, 67 expectHdr: rpc.Header{ 68 RequestId: 4, 69 Request: rpc.Request{ 70 Type: "foo", 71 Version: 2, 72 Id: "id", 73 Action: "frob", 74 }, 75 }, 76 expectBody: &value{X: "param"}, 77 }, { 78 msg: `{"request-id": 1, "type": "foo", "id": "id", "request": "frob", "params": {"X": "param"}}`, 79 expectHdr: rpc.Header{ 80 RequestId: 1, 81 Request: rpc.Request{ 82 Type: "foo", 83 Id: "id", 84 Action: "frob", 85 }, 86 Version: 1, 87 }, 88 expectBody: &value{X: "param"}, 89 }, { 90 msg: `{"request-id": 2, "error": "an error", "error-code": "a code"}`, 91 expectHdr: rpc.Header{ 92 RequestId: 2, 93 Error: "an error", 94 ErrorCode: "a code", 95 Version: 1, 96 }, 97 expectBody: new(map[string]interface{}), 98 }, { 99 msg: `{"request-id": 2, "error": "an error", "error-code": "a code", "error-info": {"foo": "bar", "baz": true}}`, 100 expectHdr: rpc.Header{ 101 RequestId: 2, 102 Error: "an error", 103 ErrorCode: "a code", 104 ErrorInfo: map[string]interface{}{ 105 "foo": "bar", 106 "baz": true, 107 }, 108 Version: 1, 109 }, 110 expectBody: new(map[string]interface{}), 111 }, { 112 msg: `{"request-id": 3, "response": {"X": "result"}}`, 113 expectHdr: rpc.Header{ 114 RequestId: 3, 115 Version: 1, 116 }, 117 expectBody: &value{X: "result"}, 118 }, { 119 msg: `{"request-id": 4, "type": "foo", "version": 2, "id": "id", "request": "frob", "params": {"X": "param"}}`, 120 expectHdr: rpc.Header{ 121 RequestId: 4, 122 Request: rpc.Request{ 123 Type: "foo", 124 Version: 2, 125 Id: "id", 126 Action: "frob", 127 }, 128 Version: 1, 129 }, 130 expectBody: &value{X: "param"}, 131 }} { 132 c.Logf("test %d", i) 133 codec := jsoncodec.New(&testConn{ 134 readMsgs: []string{test.msg}, 135 }) 136 var hdr rpc.Header 137 err := codec.ReadHeader(&hdr) 138 c.Assert(err, jc.ErrorIsNil) 139 c.Assert(hdr, gc.DeepEquals, test.expectHdr) 140 141 c.Assert(hdr.IsRequest(), gc.Equals, test.expectHdr.IsRequest()) 142 143 body := reflect.New(reflect.ValueOf(test.expectBody).Type().Elem()).Interface() 144 err = codec.ReadBody(body, test.expectHdr.IsRequest()) 145 c.Assert(err, jc.ErrorIsNil) 146 c.Assert(body, gc.DeepEquals, test.expectBody) 147 148 err = codec.ReadHeader(&hdr) 149 c.Assert(err, gc.Equals, io.EOF) 150 } 151 } 152 153 func (*suite) TestErrorAfterClose(c *gc.C) { 154 conn := &testConn{ 155 err: errors.New("some error"), 156 } 157 codec := jsoncodec.New(conn) 158 var hdr rpc.Header 159 err := codec.ReadHeader(&hdr) 160 c.Assert(err, gc.ErrorMatches, "error receiving message: some error") 161 162 err = codec.Close() 163 c.Assert(err, jc.ErrorIsNil) 164 c.Assert(conn.closed, jc.IsTrue) 165 166 err = codec.ReadHeader(&hdr) 167 c.Assert(err, gc.Equals, io.EOF) 168 } 169 170 func (*suite) TestWrite(c *gc.C) { 171 for i, test := range []struct { 172 hdr *rpc.Header 173 body interface{} 174 isRequest bool 175 expect string 176 }{{ 177 hdr: &rpc.Header{ 178 RequestId: 1, 179 Request: rpc.Request{ 180 Type: "foo", 181 Id: "id", 182 Action: "frob", 183 }, 184 }, 185 body: &value{X: "param"}, 186 expect: `{"RequestId": 1, "Type": "foo","Id":"id", "Request": "frob", "Params": {"X": "param"}}`, 187 }, { 188 hdr: &rpc.Header{ 189 RequestId: 2, 190 Error: "an error", 191 ErrorCode: "a code", 192 }, 193 expect: `{"RequestId": 2, "Error": "an error", "ErrorCode": "a code"}`, 194 }, { 195 hdr: &rpc.Header{ 196 RequestId: 2, 197 Error: "an error", 198 ErrorCode: "a code", 199 ErrorInfo: map[string]interface{}{ 200 "ignored": "for version0", 201 }, 202 }, 203 expect: `{"RequestId": 2, "Error": "an error", "ErrorCode": "a code"}`, 204 }, { 205 hdr: &rpc.Header{ 206 RequestId: 3, 207 }, 208 body: &value{X: "result"}, 209 expect: `{"RequestId": 3, "Response": {"X": "result"}}`, 210 }, { 211 hdr: &rpc.Header{ 212 RequestId: 4, 213 Request: rpc.Request{ 214 Type: "foo", 215 Version: 2, 216 Id: "", 217 Action: "frob", 218 }, 219 }, 220 body: &value{X: "param"}, 221 expect: `{"RequestId": 4, "Type": "foo", "Version": 2, "Request": "frob", "Params": {"X": "param"}}`, 222 }, { 223 hdr: &rpc.Header{ 224 RequestId: 1, 225 Request: rpc.Request{ 226 Type: "foo", 227 Id: "id", 228 Action: "frob", 229 }, 230 Version: 1, 231 }, 232 body: &value{X: "param"}, 233 expect: `{"request-id": 1, "type": "foo","id":"id", "request": "frob", "params": {"X": "param"}}`, 234 }, { 235 hdr: &rpc.Header{ 236 RequestId: 2, 237 Error: "an error", 238 ErrorCode: "a code", 239 Version: 1, 240 }, 241 expect: `{"request-id": 2, "error": "an error", "error-code": "a code"}`, 242 }, { 243 hdr: &rpc.Header{ 244 RequestId: 2, 245 Error: "an error", 246 ErrorCode: "a code", 247 ErrorInfo: map[string]interface{}{ 248 "foo": "bar", 249 "baz": true, 250 }, 251 Version: 1, 252 }, 253 expect: `{"request-id": 2, "error": "an error", "error-code": "a code", "error-info": {"foo": "bar", "baz": true}}`, 254 }, { 255 hdr: &rpc.Header{ 256 RequestId: 3, 257 Version: 1, 258 }, 259 body: &value{X: "result"}, 260 expect: `{"request-id": 3, "response": {"X": "result"}}`, 261 }, { 262 hdr: &rpc.Header{ 263 RequestId: 4, 264 Request: rpc.Request{ 265 Type: "foo", 266 Version: 2, 267 Id: "", 268 Action: "frob", 269 }, 270 Version: 1, 271 }, 272 body: &value{X: "param"}, 273 expect: `{"request-id": 4, "type": "foo", "version": 2, "request": "frob", "params": {"X": "param"}}`, 274 }} { 275 c.Logf("test %d", i) 276 var conn testConn 277 codec := jsoncodec.New(&conn) 278 err := codec.WriteMessage(test.hdr, test.body) 279 c.Assert(err, jc.ErrorIsNil) 280 c.Assert(conn.writeMsgs, gc.HasLen, 1) 281 282 assertJSONEqual(c, conn.writeMsgs[0], test.expect) 283 } 284 } 285 286 func (*suite) TestDumpRequest(c *gc.C) { 287 for i, test := range []struct { 288 hdr rpc.Header 289 body interface{} 290 expect string 291 }{{ 292 hdr: rpc.Header{ 293 RequestId: 1, 294 Request: rpc.Request{ 295 Type: "Foo", 296 Id: "id", 297 Action: "Something", 298 }, 299 }, 300 body: struct{ Arg string }{Arg: "an arg"}, 301 expect: `{"RequestId":1,"Type":"Foo","Id":"id","Request":"Something","Params":{"Arg":"an arg"}}`, 302 }, { 303 hdr: rpc.Header{ 304 RequestId: 2, 305 }, 306 body: struct{ Ret string }{Ret: "return value"}, 307 expect: `{"RequestId":2,"Response":{"Ret":"return value"}}`, 308 }, { 309 hdr: rpc.Header{ 310 RequestId: 3, 311 }, 312 expect: `{"RequestId":3}`, 313 }, { 314 hdr: rpc.Header{ 315 RequestId: 4, 316 Error: "an error", 317 ErrorCode: "an error code", 318 }, 319 expect: `{"RequestId":4,"Error":"an error","ErrorCode":"an error code"}`, 320 }, { 321 hdr: rpc.Header{ 322 RequestId: 5, 323 }, 324 body: make(chan int), 325 expect: `"marshal error: json: unsupported type: chan int"`, 326 }, { 327 hdr: rpc.Header{ 328 RequestId: 1, 329 Request: rpc.Request{ 330 Type: "Foo", 331 Version: 2, 332 Id: "id", 333 Action: "Something", 334 }, 335 }, 336 body: struct{ Arg string }{Arg: "an arg"}, 337 expect: `{"RequestId":1,"Type":"Foo","Version":2,"Id":"id","Request":"Something","Params":{"Arg":"an arg"}}`, 338 }, { 339 hdr: rpc.Header{ 340 RequestId: 1, 341 Request: rpc.Request{ 342 Type: "Foo", 343 Id: "id", 344 Action: "Something", 345 }, 346 Version: 1, 347 }, 348 body: struct{ Arg string }{Arg: "an arg"}, 349 expect: `{"request-id":1,"type":"Foo","id":"id","request":"Something","params":{"Arg":"an arg"}}`, 350 }, { 351 hdr: rpc.Header{ 352 RequestId: 2, 353 Version: 1, 354 }, 355 body: struct{ Ret string }{Ret: "return value"}, 356 expect: `{"request-id":2,"response":{"Ret":"return value"}}`, 357 }, { 358 hdr: rpc.Header{ 359 RequestId: 3, 360 Version: 1, 361 }, 362 expect: `{"request-id":3}`, 363 }, { 364 hdr: rpc.Header{ 365 RequestId: 4, 366 Error: "an error", 367 ErrorCode: "an error code", 368 Version: 1, 369 }, 370 expect: `{"request-id":4,"error":"an error","error-code":"an error code"}`, 371 }, { 372 hdr: rpc.Header{ 373 RequestId: 5, 374 Version: 1, 375 }, 376 body: make(chan int), 377 expect: `"marshal error: json: unsupported type: chan int"`, 378 }, { 379 hdr: rpc.Header{ 380 RequestId: 1, 381 Request: rpc.Request{ 382 Type: "Foo", 383 Version: 2, 384 Id: "id", 385 Action: "Something", 386 }, 387 Version: 1, 388 }, 389 body: struct{ Arg string }{Arg: "an arg"}, 390 expect: `{"request-id":1,"type":"Foo","version":2,"id":"id","request":"Something","params":{"Arg":"an arg"}}`, 391 }} { 392 c.Logf("test %d; %#v", i, test.hdr) 393 data := jsoncodec.DumpRequest(&test.hdr, test.body) 394 c.Check(string(data), gc.Equals, test.expect) 395 } 396 } 397 398 // assertJSONEqual compares the json strings v0 399 // and v1 ignoring white space. 400 func assertJSONEqual(c *gc.C, v0, v1 string) { 401 var m0, m1 interface{} 402 err := json.Unmarshal([]byte(v0), &m0) 403 c.Assert(err, jc.ErrorIsNil) 404 err = json.Unmarshal([]byte(v1), &m1) 405 c.Assert(err, jc.ErrorIsNil) 406 data0, err := json.Marshal(m0) 407 c.Assert(err, jc.ErrorIsNil) 408 data1, err := json.Marshal(m1) 409 c.Assert(err, jc.ErrorIsNil) 410 c.Assert(string(data0), gc.Equals, string(data1)) 411 } 412 413 type testConn struct { 414 readMsgs []string 415 err error 416 writeMsgs []string 417 closed bool 418 } 419 420 func (c *testConn) Receive(msg interface{}) error { 421 if len(c.readMsgs) > 0 { 422 s := c.readMsgs[0] 423 c.readMsgs = c.readMsgs[1:] 424 return json.Unmarshal([]byte(s), msg) 425 } 426 if c.err != nil { 427 return c.err 428 } 429 return io.EOF 430 } 431 432 func (c *testConn) Send(msg interface{}) error { 433 data, err := json.Marshal(msg) 434 if err != nil { 435 return err 436 } 437 c.writeMsgs = append(c.writeMsgs, string(data)) 438 return nil 439 } 440 441 func (c *testConn) Close() error { 442 c.closed = true 443 return nil 444 }