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  }