github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/rpc/params/params_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package params_test
     5  
     6  import (
     7  	"encoding/json"
     8  	stdtesting "testing"
     9  
    10  	"github.com/juju/charm/v12"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	"github.com/juju/juju/core/constraints"
    15  	"github.com/juju/juju/core/instance"
    16  	"github.com/juju/juju/core/life"
    17  	"github.com/juju/juju/core/model"
    18  	"github.com/juju/juju/core/status"
    19  	"github.com/juju/juju/rpc/params"
    20  	"github.com/juju/juju/testing"
    21  )
    22  
    23  // TestPackage integrates the tests into gotest.
    24  func TestPackage(t *stdtesting.T) {
    25  	gc.TestingT(t)
    26  }
    27  
    28  type MarshalSuite struct{}
    29  
    30  var _ = gc.Suite(&MarshalSuite{})
    31  
    32  var marshalTestCases = []struct {
    33  	about string
    34  	// Value holds a real Go struct.
    35  	value params.Delta
    36  	// JSON document.
    37  	json string
    38  }{{
    39  	about: "MachineInfo Delta",
    40  	value: params.Delta{
    41  		Entity: &params.MachineInfo{
    42  			ModelUUID:  "uuid",
    43  			Id:         "Benji",
    44  			InstanceId: "Shazam",
    45  			AgentStatus: params.StatusInfo{
    46  				Current: status.Error,
    47  				Message: "foo",
    48  			},
    49  			InstanceStatus: params.StatusInfo{
    50  				Current: status.Pending,
    51  			},
    52  			Life:                    life.Alive,
    53  			Base:                    "ubuntu@22.04",
    54  			SupportedContainers:     []instance.ContainerType{instance.LXD},
    55  			Jobs:                    []model.MachineJob{model.JobManageModel},
    56  			Addresses:               []params.Address{},
    57  			HardwareCharacteristics: &instance.HardwareCharacteristics{},
    58  		},
    59  	},
    60  	json: `["machine","change",{"model-uuid":"uuid","id":"Benji","instance-id":"Shazam","container-type":"","agent-status":{"current":"error","message":"foo","version":""},"instance-status":{"current":"pending","message":"","version":""},"life":"alive","base":"ubuntu@22.04","supported-containers":["lxd"],"supported-containers-known":false,"hardware-characteristics":{},"jobs":["JobManageModel"],"addresses":[],"has-vote":false,"wants-vote":false}]`,
    61  }, {
    62  	about: "ApplicationInfo Delta",
    63  	value: params.Delta{
    64  		Entity: &params.ApplicationInfo{
    65  			ModelUUID:   "uuid",
    66  			Name:        "Benji",
    67  			Exposed:     true,
    68  			CharmURL:    "ch:quantal/name",
    69  			Life:        life.Dying,
    70  			OwnerTag:    "test-owner",
    71  			MinUnits:    42,
    72  			Constraints: constraints.MustParse("arch=s390x mem=1024M"),
    73  			Config: charm.Settings{
    74  				"hello": "goodbye",
    75  				"foo":   false,
    76  			},
    77  			Status: params.StatusInfo{
    78  				Current: status.Active,
    79  				Message: "all good",
    80  			},
    81  			WorkloadVersion: "42.47",
    82  		},
    83  	},
    84  	json: `["application","change",{"model-uuid": "uuid", "charm-url": "ch:quantal/name","name":"Benji","exposed":true,"life":"dying","owner-tag":"test-owner","workload-version":"42.47","min-units":42,"constraints":{"arch":"s390x", "mem": 1024},"config": {"hello":"goodbye","foo":false},"subordinate":false,"status":{"current":"active", "message":"all good", "version": ""}}]`,
    85  }, {
    86  	about: "CharmInfo Delta",
    87  	value: params.Delta{
    88  		Entity: &params.CharmInfo{
    89  			ModelUUID:    "uuid",
    90  			CharmURL:     "ch:quantal/name",
    91  			Life:         life.Dying,
    92  			CharmVersion: "3",
    93  			LXDProfile:   &params.Profile{},
    94  		},
    95  	},
    96  	json: `["charm","change",{"model-uuid": "uuid", "charm-url": "ch:quantal/name", "charm-version":"3", "life":"dying","profile":{}}]`,
    97  }, {
    98  	about: "UnitInfo Delta",
    99  	value: params.Delta{
   100  		Entity: &params.UnitInfo{
   101  			ModelUUID:   "uuid",
   102  			Name:        "Benji",
   103  			Application: "Shazam",
   104  			Base:        "ubuntu@12.04",
   105  			CharmURL:    "ch:precise/wordpress-42",
   106  			Life:        life.Alive,
   107  			Ports: []params.Port{{
   108  				Protocol: "http",
   109  				Number:   80,
   110  			}},
   111  			PortRanges: []params.PortRange{{
   112  				FromPort: 80,
   113  				ToPort:   80,
   114  				Protocol: "http",
   115  			}},
   116  			PublicAddress:  "testing.invalid",
   117  			PrivateAddress: "10.0.0.1",
   118  			MachineId:      "1",
   119  			WorkloadStatus: params.StatusInfo{
   120  				Current: status.Active,
   121  				Message: "all good",
   122  			},
   123  			AgentStatus: params.StatusInfo{
   124  				Current: status.Idle,
   125  			},
   126  		},
   127  	},
   128  	json: `["unit","change",{"model-uuid":"uuid","name":"Benji","application":"Shazam","base":"ubuntu@12.04","charm-url":"ch:precise/wordpress-42","life":"alive","public-address":"testing.invalid","private-address":"10.0.0.1","machine-id":"1","principal":"","ports":[{"protocol":"http","number":80}],"port-ranges":[{"from-port":80,"to-port":80,"protocol":"http"}],"subordinate":false,"workload-status":{"current":"active","message":"all good","version":""},"agent-status":{"current":"idle","message":"","version":""}}]`,
   129  }, {
   130  	about: "RelationInfo Delta",
   131  	value: params.Delta{
   132  		Entity: &params.RelationInfo{
   133  			ModelUUID: "uuid",
   134  			Key:       "Benji",
   135  			Id:        4711,
   136  			Endpoints: []params.Endpoint{
   137  				{
   138  					ApplicationName: "logging",
   139  					Relation: params.CharmRelation{
   140  						Name:      "logging-directory",
   141  						Role:      "requirer",
   142  						Interface: "logging",
   143  						Optional:  false,
   144  						Limit:     1,
   145  						Scope:     "container"},
   146  				},
   147  				{
   148  					ApplicationName: "wordpress",
   149  					Relation: params.CharmRelation{
   150  						Name:      "logging-dir",
   151  						Role:      "provider",
   152  						Interface: "logging",
   153  						Optional:  false,
   154  						Limit:     0,
   155  						Scope:     "container"},
   156  				},
   157  			},
   158  		},
   159  	},
   160  	json: `["relation","change",{"model-uuid": "uuid", "key":"Benji", "id": 4711, "endpoints": [{"application-name":"logging", "relation":{"name":"logging-directory", "role":"requirer", "interface":"logging", "optional":false, "limit":1, "scope":"container"}}, {"application-name":"wordpress", "relation":{"name":"logging-dir", "role":"provider", "interface":"logging", "optional":false, "limit":0, "scope":"container"}}]}]`,
   161  }, {
   162  	about: "AnnotationInfo Delta",
   163  	value: params.Delta{
   164  		Entity: &params.AnnotationInfo{
   165  			ModelUUID: "uuid",
   166  			Tag:       "machine-0",
   167  			Annotations: map[string]string{
   168  				"foo":   "bar",
   169  				"arble": "2 4",
   170  			},
   171  		},
   172  	},
   173  	json: `["annotation","change",{"model-uuid": "uuid", "tag":"machine-0","annotations":{"foo":"bar","arble":"2 4"}}]`,
   174  }, {
   175  	about: "Delta Removed True",
   176  	value: params.Delta{
   177  		Removed: true,
   178  		Entity: &params.RelationInfo{
   179  			ModelUUID: "uuid",
   180  			Key:       "Benji",
   181  		},
   182  	},
   183  	json: `["relation","remove",{"model-uuid": "uuid", "key":"Benji", "id": 0, "endpoints": null}]`,
   184  }}
   185  
   186  func (s *MarshalSuite) TestDeltaMarshalJSON(c *gc.C) {
   187  	for _, t := range marshalTestCases {
   188  		c.Log(t.about)
   189  		output, err := t.value.MarshalJSON()
   190  		c.Check(err, jc.ErrorIsNil)
   191  		// We check unmarshalled output both to reduce the fragility of the
   192  		// tests (because ordering in the maps can change) and to verify that
   193  		// the output is well-formed.
   194  		var unmarshalledOutput interface{}
   195  		err = json.Unmarshal(output, &unmarshalledOutput)
   196  		c.Check(err, jc.ErrorIsNil)
   197  		var expected interface{}
   198  		err = json.Unmarshal([]byte(t.json), &expected)
   199  		c.Check(err, jc.ErrorIsNil)
   200  		c.Check(unmarshalledOutput, jc.DeepEquals, expected)
   201  	}
   202  }
   203  
   204  func (s *MarshalSuite) TestDeltaUnmarshalJSON(c *gc.C) {
   205  	for i, t := range marshalTestCases {
   206  		c.Logf("test %d. %s", i, t.about)
   207  		var unmarshalled params.Delta
   208  		err := json.Unmarshal([]byte(t.json), &unmarshalled)
   209  		c.Check(err, jc.ErrorIsNil)
   210  		c.Check(unmarshalled, jc.DeepEquals, t.value)
   211  	}
   212  }
   213  
   214  func (s *MarshalSuite) TestDeltaMarshalJSONCardinality(c *gc.C) {
   215  	err := json.Unmarshal([]byte(`[1,2]`), new(params.Delta))
   216  	c.Check(err, gc.ErrorMatches, "Expected 3 elements in top-level of JSON but got 2")
   217  }
   218  
   219  func (s *MarshalSuite) TestDeltaMarshalJSONUnknownOperation(c *gc.C) {
   220  	err := json.Unmarshal([]byte(`["relation","masticate",{}]`), new(params.Delta))
   221  	c.Check(err, gc.ErrorMatches, `Unexpected operation "masticate"`)
   222  }
   223  
   224  func (s *MarshalSuite) TestDeltaMarshalJSONUnknownEntity(c *gc.C) {
   225  	err := json.Unmarshal([]byte(`["qwan","change",{}]`), new(params.Delta))
   226  	c.Check(err, gc.ErrorMatches, `Unexpected entity name "qwan"`)
   227  }
   228  
   229  type ErrorResultsSuite struct{}
   230  
   231  var _ = gc.Suite(&ErrorResultsSuite{})
   232  
   233  func (s *ErrorResultsSuite) TestOneError(c *gc.C) {
   234  	for i, test := range []struct {
   235  		results  params.ErrorResults
   236  		errMatch string
   237  	}{
   238  		{
   239  			errMatch: "expected 1 result, got 0",
   240  		}, {
   241  			results: params.ErrorResults{
   242  				[]params.ErrorResult{{nil}},
   243  			},
   244  		}, {
   245  			results: params.ErrorResults{
   246  				[]params.ErrorResult{{nil}, {nil}},
   247  			},
   248  			errMatch: "expected 1 result, got 2",
   249  		}, {
   250  			results: params.ErrorResults{
   251  				[]params.ErrorResult{
   252  					{&params.Error{Message: "test error"}},
   253  				},
   254  			},
   255  			errMatch: "test error",
   256  		},
   257  	} {
   258  		c.Logf("test %d", i)
   259  		err := test.results.OneError()
   260  		if test.errMatch == "" {
   261  			c.Check(err, jc.ErrorIsNil)
   262  		} else {
   263  			c.Check(err, gc.ErrorMatches, test.errMatch)
   264  		}
   265  	}
   266  }
   267  
   268  func (s *ErrorResultsSuite) TestCombine(c *gc.C) {
   269  	for i, test := range []struct {
   270  		msg      string
   271  		results  params.ErrorResults
   272  		errMatch string
   273  	}{
   274  		{
   275  			msg: "no results, no error",
   276  		}, {
   277  			msg: "single nil result",
   278  			results: params.ErrorResults{
   279  				[]params.ErrorResult{{nil}},
   280  			},
   281  		}, {
   282  			msg: "multiple nil results",
   283  			results: params.ErrorResults{
   284  				[]params.ErrorResult{{nil}, {nil}},
   285  			},
   286  		}, {
   287  			msg: "one error result",
   288  			results: params.ErrorResults{
   289  				[]params.ErrorResult{
   290  					{&params.Error{Message: "test error"}},
   291  				},
   292  			},
   293  			errMatch: "test error",
   294  		}, {
   295  			msg: "mixed error results",
   296  			results: params.ErrorResults{
   297  				[]params.ErrorResult{
   298  					{&params.Error{Message: "test error"}},
   299  					{nil},
   300  					{&params.Error{Message: "second error"}},
   301  				},
   302  			},
   303  			errMatch: "test error\nsecond error",
   304  		},
   305  	} {
   306  		c.Logf("test %d: %s", i, test.msg)
   307  		err := test.results.Combine()
   308  		if test.errMatch == "" {
   309  			c.Check(err, jc.ErrorIsNil)
   310  		} else {
   311  			c.Check(err, gc.ErrorMatches, test.errMatch)
   312  		}
   313  	}
   314  }
   315  
   316  type importSuite struct{}
   317  
   318  var _ = gc.Suite(&importSuite{})
   319  
   320  func (*importSuite) TestParamsDoesNotDependOnState(c *gc.C) {
   321  	imports := testing.FindJujuCoreImports(c, "github.com/juju/juju/rpc/params")
   322  	for _, i := range imports {
   323  		c.Assert(i, gc.Not(gc.Equals), "state")
   324  	}
   325  }