github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/cmd/juju/application/config_test.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 package application_test 4 5 import ( 6 "bytes" 7 "io/ioutil" 8 "os" 9 "strings" 10 "unicode/utf8" 11 12 "github.com/juju/cmd" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 goyaml "gopkg.in/yaml.v2" 17 18 "github.com/juju/juju/apiserver/common" 19 "github.com/juju/juju/cmd/juju/application" 20 coretesting "github.com/juju/juju/testing" 21 ) 22 23 type configCommandSuite struct { 24 coretesting.FakeJujuXDGDataHomeSuite 25 dir string 26 fake *fakeApplicationAPI 27 } 28 29 var ( 30 _ = gc.Suite(&configCommandSuite{}) 31 32 validSetTestValue = "a value with spaces\nand newline\nand UTF-8 characters: \U0001F604 / \U0001F44D" 33 invalidSetTestValue = "a value with an invalid UTF-8 sequence: " + string([]byte{0xFF, 0xFF}) 34 yamlConfigValue = "dummy-application:\n skill-level: 9000\n username: admin001\n\n" 35 ) 36 37 var getTests = []struct { 38 application string 39 expected map[string]interface{} 40 }{ 41 { 42 "dummy-application", 43 map[string]interface{}{ 44 "application": "dummy-application", 45 "charm": "dummy", 46 "settings": map[string]interface{}{ 47 "title": map[string]interface{}{ 48 "description": "Specifies title", 49 "type": "string", 50 "value": "Nearly There", 51 }, 52 "skill-level": map[string]interface{}{ 53 "description": "Specifies skill-level", 54 "value": 100, 55 "type": "int", 56 }, 57 "username": map[string]interface{}{ 58 "description": "Specifies username", 59 "type": "string", 60 "value": "admin001", 61 }, 62 "outlook": map[string]interface{}{ 63 "description": "Specifies outlook", 64 "type": "string", 65 "value": "true", 66 }, 67 }, 68 }, 69 }, 70 } 71 72 func (s *configCommandSuite) SetUpTest(c *gc.C) { 73 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 74 s.fake = &fakeApplicationAPI{name: "dummy-application", charmName: "dummy", 75 values: map[string]interface{}{ 76 "title": "Nearly There", 77 "skill-level": 100, 78 "username": "admin001", 79 "outlook": "true", 80 }} 81 s.FakeJujuXDGDataHomeSuite.SetUpTest(c) 82 83 s.dir = c.MkDir() 84 c.Assert(utf8.ValidString(validSetTestValue), jc.IsTrue) 85 c.Assert(utf8.ValidString(invalidSetTestValue), jc.IsFalse) 86 setupValueFile(c, s.dir, "valid.txt", validSetTestValue) 87 setupValueFile(c, s.dir, "invalid.txt", invalidSetTestValue) 88 setupBigFile(c, s.dir) 89 setupConfigFile(c, s.dir) 90 } 91 92 func (s *configCommandSuite) TestGetCommandInit(c *gc.C) { 93 // missing args 94 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{}) 95 c.Assert(err, gc.ErrorMatches, "no application name specified") 96 } 97 98 func (s *configCommandSuite) TestGetCommandInitWithApplication(c *gc.C) { 99 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app"}) 100 // everything ok 101 c.Assert(err, jc.ErrorIsNil) 102 } 103 104 func (s *configCommandSuite) TestGetCommandInitWithKey(c *gc.C) { 105 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app", "key"}) 106 // everything ok 107 c.Assert(err, jc.ErrorIsNil) 108 } 109 110 func (s *configCommandSuite) TestGetConfig(c *gc.C) { 111 for _, t := range getTests { 112 ctx := coretesting.Context(c) 113 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{t.application}) 114 c.Check(code, gc.Equals, 0) 115 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 116 // round trip via goyaml to avoid being sucked into a quagmire of 117 // map[interface{}]interface{} vs map[string]interface{}. This is 118 // also required if we add json support to this command. 119 buf, err := goyaml.Marshal(t.expected) 120 c.Assert(err, jc.ErrorIsNil) 121 expected := make(map[string]interface{}) 122 err = goyaml.Unmarshal(buf, &expected) 123 c.Assert(err, jc.ErrorIsNil) 124 125 actual := make(map[string]interface{}) 126 err = goyaml.Unmarshal(ctx.Stdout.(*bytes.Buffer).Bytes(), &actual) 127 c.Assert(err, jc.ErrorIsNil) 128 c.Assert(actual, gc.DeepEquals, expected) 129 } 130 } 131 132 func (s *configCommandSuite) TestGetConfigKey(c *gc.C) { 133 ctx := coretesting.Context(c) 134 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "title"}) 135 c.Check(code, gc.Equals, 0) 136 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 137 c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "Nearly There\n") 138 } 139 140 func (s *configCommandSuite) TestGetConfigKeyNotFound(c *gc.C) { 141 ctx := coretesting.Context(c) 142 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "invalid"}) 143 c.Check(code, gc.Equals, 1) 144 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "error: key \"invalid\" not found in \"dummy-application\" application settings.\n") 145 c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "") 146 } 147 148 func (s *configCommandSuite) TestSetCommandInit(c *gc.C) { 149 // missing args 150 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{}) 151 c.Assert(err, gc.ErrorMatches, "no application name specified") 152 153 // missing application name 154 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"name=foo"}) 155 c.Assert(err, gc.ErrorMatches, "no application name specified") 156 157 // --file path, but no application 158 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"--file", "testconfig.yaml"}) 159 c.Assert(err, gc.ErrorMatches, "no application name specified") 160 161 // --file and options specified 162 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--file", "testconfig.yaml", "bees="}) 163 c.Assert(err, gc.ErrorMatches, "cannot specify --file and key=value arguments simultaneously") 164 165 // --reset and no config name provided 166 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset"}) 167 c.Assert(err, gc.ErrorMatches, "flag needs an argument: --reset") 168 169 // cannot set and retrieve simultaneously 170 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "get", "set=value"}) 171 c.Assert(err, gc.ErrorMatches, "cannot set and retrieve values simultaneously") 172 173 // cannot reset and get simultaneously 174 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset", "reset", "get"}) 175 c.Assert(err, gc.ErrorMatches, "cannot reset and retrieve values simultaneously") 176 177 // invalid reset keys 178 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset", "reset,bad=key"}) 179 c.Assert(err, gc.ErrorMatches, `--reset accepts a comma delimited set of keys "a,b,c", received: "bad=key"`) 180 181 // init too many args fails 182 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "key", "another"}) 183 c.Assert(err, gc.ErrorMatches, "can only retrieve a single value, or all values") 184 185 } 186 187 func (s *configCommandSuite) TestSetOptionSuccess(c *gc.C) { 188 s.assertSetSuccess(c, s.dir, []string{ 189 "username=hello", 190 "outlook=hello@world.tld", 191 }, map[string]interface{}{ 192 "username": "hello", 193 "outlook": "hello@world.tld", 194 }) 195 s.assertSetSuccess(c, s.dir, []string{ 196 "username=hello=foo", 197 }, map[string]interface{}{ 198 "username": "hello=foo", 199 "outlook": "hello@world.tld", 200 }) 201 s.assertSetSuccess(c, s.dir, []string{ 202 "username=@valid.txt", 203 }, map[string]interface{}{ 204 "username": validSetTestValue, 205 "outlook": "hello@world.tld", 206 }) 207 s.assertSetSuccess(c, s.dir, []string{ 208 "username=", 209 }, map[string]interface{}{ 210 "username": "", 211 "outlook": "hello@world.tld", 212 }) 213 } 214 215 func (s *configCommandSuite) TestSetSameValue(c *gc.C) { 216 s.assertSetSuccess(c, s.dir, []string{ 217 "username=hello", 218 "outlook=hello@world.tld", 219 }, map[string]interface{}{ 220 "username": "hello", 221 "outlook": "hello@world.tld", 222 }) 223 s.assertSetWarning(c, s.dir, []string{ 224 "username=hello", 225 }, "the configuration setting \"username\" already has the value \"hello\"") 226 s.assertSetWarning(c, s.dir, []string{ 227 "outlook=hello@world.tld", 228 }, "the configuration setting \"outlook\" already has the value \"hello@world.tld\"") 229 230 } 231 232 func (s *configCommandSuite) TestSetOptionFail(c *gc.C) { 233 s.assertSetFail(c, s.dir, []string{"foo", "bar"}, 234 "error: can only retrieve a single value, or all values\n") 235 s.assertSetFail(c, s.dir, []string{"=bar"}, "error: expected \"key=value\", got \"=bar\"\n") 236 s.assertSetFail(c, s.dir, []string{ 237 "username=@missing.txt", 238 }, "error: cannot read option from file \"missing.txt\": .* "+utils.NoSuchFileErrRegexp+"\n") 239 s.assertSetFail(c, s.dir, []string{ 240 "username=@big.txt", 241 }, "error: size of option file is larger than 5M\n") 242 s.assertSetFail(c, s.dir, []string{ 243 "username=@invalid.txt", 244 }, "error: value for option \"username\" contains non-UTF-8 sequences\n") 245 } 246 247 func (s *configCommandSuite) TestSetConfig(c *gc.C) { 248 s.assertSetFail(c, s.dir, []string{ 249 "--file", 250 "missing.yaml", 251 }, "error.* "+utils.NoSuchFileErrRegexp+"\n") 252 253 ctx := coretesting.ContextForDir(c, s.dir) 254 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 255 "dummy-application", 256 "--file", 257 "testconfig.yaml"}) 258 259 c.Check(code, gc.Equals, 0) 260 c.Check(s.fake.config, gc.Equals, yamlConfigValue) 261 } 262 263 func (s *configCommandSuite) TestSetFromStdin(c *gc.C) { 264 s.fake = &fakeApplicationAPI{name: "dummy-application"} 265 ctx := coretesting.Context(c) 266 ctx.Stdin = strings.NewReader("settings:\n username:\n value: world\n") 267 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 268 "dummy-application", 269 "--file", 270 "-"}) 271 272 c.Check(code, gc.Equals, 0) 273 c.Check(s.fake.config, jc.DeepEquals, "settings:\n username:\n value: world\n") 274 } 275 276 func (s *configCommandSuite) TestResetConfigToDefault(c *gc.C) { 277 s.fake = &fakeApplicationAPI{name: "dummy-application", values: map[string]interface{}{ 278 "username": "hello", 279 }} 280 s.assertSetSuccess(c, s.dir, []string{ 281 "--reset", 282 "username", 283 }, make(map[string]interface{})) 284 } 285 286 func (s *configCommandSuite) TestBlockSetConfig(c *gc.C) { 287 // Block operation 288 s.fake.err = common.OperationBlockedError("TestBlockSetConfig") 289 ctx := coretesting.ContextForDir(c, s.dir) 290 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 291 "dummy-application", 292 "--file", 293 "testconfig.yaml"}) 294 c.Check(code, gc.Equals, 1) 295 // msg is logged 296 stripped := strings.Replace(c.GetTestLog(), "\n", "", -1) 297 c.Check(stripped, gc.Matches, ".*TestBlockSetConfig.*") 298 } 299 300 // assertSetSuccess sets configuration options and checks the expected settings. 301 func (s *configCommandSuite) assertSetSuccess(c *gc.C, dir string, args []string, expect map[string]interface{}) { 302 ctx := coretesting.ContextForDir(c, dir) 303 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 304 c.Assert(code, gc.Equals, 0) 305 } 306 307 // assertSetFail sets configuration options and checks the expected error. 308 func (s *configCommandSuite) assertSetFail(c *gc.C, dir string, args []string, err string) { 309 ctx := coretesting.ContextForDir(c, dir) 310 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 311 c.Check(code, gc.Not(gc.Equals), 0) 312 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Matches, err) 313 } 314 315 func (s *configCommandSuite) assertSetWarning(c *gc.C, dir string, args []string, w string) { 316 ctx := coretesting.ContextForDir(c, dir) 317 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 318 c.Check(code, gc.Equals, 0) 319 320 c.Assert(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, ".*WARNING.*"+w+".*") 321 } 322 323 // setupValueFile creates a file containing one value for testing 324 // set with name=@filename. 325 func setupValueFile(c *gc.C, dir, filename, value string) string { 326 ctx := coretesting.ContextForDir(c, dir) 327 path := ctx.AbsPath(filename) 328 content := []byte(value) 329 err := ioutil.WriteFile(path, content, 0666) 330 c.Assert(err, jc.ErrorIsNil) 331 return path 332 } 333 334 // setupBigFile creates a too big file for testing 335 // set with name=@filename. 336 func setupBigFile(c *gc.C, dir string) string { 337 ctx := coretesting.ContextForDir(c, dir) 338 path := ctx.AbsPath("big.txt") 339 file, err := os.Create(path) 340 c.Assert(err, jc.ErrorIsNil) 341 defer file.Close() 342 chunk := make([]byte, 1024) 343 for i := 0; i < cap(chunk); i++ { 344 chunk[i] = byte(i % 256) 345 } 346 for i := 0; i < 6000; i++ { 347 _, err = file.Write(chunk) 348 c.Assert(err, jc.ErrorIsNil) 349 } 350 return path 351 } 352 353 // setupConfigFile creates a configuration file for testing set 354 // with the --file argument specifying a configuration file. 355 func setupConfigFile(c *gc.C, dir string) string { 356 ctx := coretesting.ContextForDir(c, dir) 357 path := ctx.AbsPath("testconfig.yaml") 358 content := []byte(yamlConfigValue) 359 err := ioutil.WriteFile(path, content, 0666) 360 c.Assert(err, jc.ErrorIsNil) 361 return path 362 }