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: ¶ms.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: ¶ms.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: ¶ms.CharmInfo{ 89 ModelUUID: "uuid", 90 CharmURL: "ch:quantal/name", 91 Life: life.Dying, 92 CharmVersion: "3", 93 LXDProfile: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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: ¶ms.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 {¶ms.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 {¶ms.Error{Message: "test error"}}, 291 }, 292 }, 293 errMatch: "test error", 294 }, { 295 msg: "mixed error results", 296 results: params.ErrorResults{ 297 []params.ErrorResult{ 298 {¶ms.Error{Message: "test error"}}, 299 {nil}, 300 {¶ms.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 }