github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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) TestGetCommandInitTooManyArgs(c *gc.C) { 111 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app", "key", "another"}) 112 c.Assert(err, gc.ErrorMatches, "can only retrieve a single value, or all values") 113 } 114 115 func (s *configCommandSuite) TestGetConfig(c *gc.C) { 116 for _, t := range getTests { 117 ctx := coretesting.Context(c) 118 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{t.application}) 119 c.Check(code, gc.Equals, 0) 120 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 121 // round trip via goyaml to avoid being sucked into a quagmire of 122 // map[interface{}]interface{} vs map[string]interface{}. This is 123 // also required if we add json support to this command. 124 buf, err := goyaml.Marshal(t.expected) 125 c.Assert(err, jc.ErrorIsNil) 126 expected := make(map[string]interface{}) 127 err = goyaml.Unmarshal(buf, &expected) 128 c.Assert(err, jc.ErrorIsNil) 129 130 actual := make(map[string]interface{}) 131 err = goyaml.Unmarshal(ctx.Stdout.(*bytes.Buffer).Bytes(), &actual) 132 c.Assert(err, jc.ErrorIsNil) 133 c.Assert(actual, gc.DeepEquals, expected) 134 } 135 } 136 137 func (s *configCommandSuite) TestGetConfigKey(c *gc.C) { 138 ctx := coretesting.Context(c) 139 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "title"}) 140 c.Check(code, gc.Equals, 0) 141 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "") 142 c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "Nearly There\n") 143 } 144 145 func (s *configCommandSuite) TestGetConfigKeyNotFound(c *gc.C) { 146 ctx := coretesting.Context(c) 147 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{"dummy-application", "invalid"}) 148 c.Check(code, gc.Equals, 1) 149 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Equals, "error: key \"invalid\" not found in \"dummy-application\" application settings.\n") 150 c.Assert(ctx.Stdout.(*bytes.Buffer).String(), gc.Equals, "") 151 } 152 153 func (s *configCommandSuite) TestSetCommandInit(c *gc.C) { 154 // missing args 155 err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{}) 156 c.Assert(err, gc.ErrorMatches, "no application name specified") 157 158 // missing application name 159 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"name=foo"}) 160 c.Assert(err, gc.ErrorMatches, "no application name specified") 161 162 // --file path, but no application 163 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"--file", "testconfig.yaml"}) 164 c.Assert(err, gc.ErrorMatches, "no application name specified") 165 166 // --file and options specified 167 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--file", "testconfig.yaml", "bees="}) 168 c.Assert(err, gc.ErrorMatches, "cannot specify --file and key=value arguments simultaneously") 169 170 // --reset and no config name provided 171 err = coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"application", "--reset"}) 172 c.Assert(err, gc.ErrorMatches, "no configuration options specified") 173 174 } 175 176 func (s *configCommandSuite) TestSetOptionSuccess(c *gc.C) { 177 s.assertSetSuccess(c, s.dir, []string{ 178 "username=hello", 179 "outlook=hello@world.tld", 180 }, map[string]interface{}{ 181 "username": "hello", 182 "outlook": "hello@world.tld", 183 }) 184 s.assertSetSuccess(c, s.dir, []string{ 185 "username=hello=foo", 186 }, map[string]interface{}{ 187 "username": "hello=foo", 188 "outlook": "hello@world.tld", 189 }) 190 s.assertSetSuccess(c, s.dir, []string{ 191 "username=@valid.txt", 192 }, map[string]interface{}{ 193 "username": validSetTestValue, 194 "outlook": "hello@world.tld", 195 }) 196 s.assertSetSuccess(c, s.dir, []string{ 197 "username=", 198 }, map[string]interface{}{ 199 "username": "", 200 "outlook": "hello@world.tld", 201 }) 202 } 203 204 func (s *configCommandSuite) TestSetSameValue(c *gc.C) { 205 s.assertSetSuccess(c, s.dir, []string{ 206 "username=hello", 207 "outlook=hello@world.tld", 208 }, map[string]interface{}{ 209 "username": "hello", 210 "outlook": "hello@world.tld", 211 }) 212 s.assertSetWarning(c, s.dir, []string{ 213 "username=hello", 214 }, "the configuration setting \"username\" already has the value \"hello\"") 215 s.assertSetWarning(c, s.dir, []string{ 216 "outlook=hello@world.tld", 217 }, "the configuration setting \"outlook\" already has the value \"hello@world.tld\"") 218 219 } 220 221 func (s *configCommandSuite) TestSetOptionFail(c *gc.C) { 222 s.assertSetFail(c, s.dir, []string{"foo", "bar"}, 223 "error: can only retrieve a single value, or all values\n") 224 s.assertSetFail(c, s.dir, []string{"=bar"}, "error: expected \"key=value\", got \"=bar\"\n") 225 s.assertSetFail(c, s.dir, []string{ 226 "username=@missing.txt", 227 }, "error: cannot read option from file \"missing.txt\": .* "+utils.NoSuchFileErrRegexp+"\n") 228 s.assertSetFail(c, s.dir, []string{ 229 "username=@big.txt", 230 }, "error: size of option file is larger than 5M\n") 231 s.assertSetFail(c, s.dir, []string{ 232 "username=@invalid.txt", 233 }, "error: value for option \"username\" contains non-UTF-8 sequences\n") 234 } 235 236 func (s *configCommandSuite) TestSetConfig(c *gc.C) { 237 s.assertSetFail(c, s.dir, []string{ 238 "--file", 239 "missing.yaml", 240 }, "error.* "+utils.NoSuchFileErrRegexp+"\n") 241 242 ctx := coretesting.ContextForDir(c, s.dir) 243 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 244 "dummy-application", 245 "--file", 246 "testconfig.yaml"}) 247 248 c.Check(code, gc.Equals, 0) 249 c.Check(s.fake.config, gc.Equals, yamlConfigValue) 250 } 251 252 func (s *configCommandSuite) TestSetFromStdin(c *gc.C) { 253 s.fake = &fakeApplicationAPI{name: "dummy-application"} 254 ctx := coretesting.Context(c) 255 ctx.Stdin = strings.NewReader("settings:\n username:\n value: world\n") 256 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 257 "dummy-application", 258 "--file", 259 "-"}) 260 261 c.Check(code, gc.Equals, 0) 262 c.Check(s.fake.config, jc.DeepEquals, "settings:\n username:\n value: world\n") 263 } 264 265 func (s *configCommandSuite) TestResetConfigToDefault(c *gc.C) { 266 s.fake = &fakeApplicationAPI{name: "dummy-application", values: map[string]interface{}{ 267 "username": "hello", 268 }} 269 s.assertSetSuccess(c, s.dir, []string{ 270 "--reset", 271 "username", 272 }, make(map[string]interface{})) 273 } 274 275 func (s *configCommandSuite) TestBlockSetConfig(c *gc.C) { 276 // Block operation 277 s.fake.err = common.OperationBlockedError("TestBlockSetConfig") 278 ctx := coretesting.ContextForDir(c, s.dir) 279 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{ 280 "dummy-application", 281 "--file", 282 "testconfig.yaml"}) 283 c.Check(code, gc.Equals, 1) 284 // msg is logged 285 stripped := strings.Replace(c.GetTestLog(), "\n", "", -1) 286 c.Check(stripped, gc.Matches, ".*TestBlockSetConfig.*") 287 } 288 289 // assertSetSuccess sets configuration options and checks the expected settings. 290 func (s *configCommandSuite) assertSetSuccess(c *gc.C, dir string, args []string, expect map[string]interface{}) { 291 ctx := coretesting.ContextForDir(c, dir) 292 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 293 c.Assert(code, gc.Equals, 0) 294 } 295 296 // assertSetFail sets configuration options and checks the expected error. 297 func (s *configCommandSuite) assertSetFail(c *gc.C, dir string, args []string, err string) { 298 ctx := coretesting.ContextForDir(c, dir) 299 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 300 c.Check(code, gc.Not(gc.Equals), 0) 301 c.Assert(ctx.Stderr.(*bytes.Buffer).String(), gc.Matches, err) 302 } 303 304 func (s *configCommandSuite) assertSetWarning(c *gc.C, dir string, args []string, w string) { 305 ctx := coretesting.ContextForDir(c, dir) 306 code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, append([]string{"dummy-application"}, args...)) 307 c.Check(code, gc.Equals, 0) 308 309 c.Assert(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, ".*WARNING.*"+w+".*") 310 } 311 312 // setupValueFile creates a file containing one value for testing 313 // set with name=@filename. 314 func setupValueFile(c *gc.C, dir, filename, value string) string { 315 ctx := coretesting.ContextForDir(c, dir) 316 path := ctx.AbsPath(filename) 317 content := []byte(value) 318 err := ioutil.WriteFile(path, content, 0666) 319 c.Assert(err, jc.ErrorIsNil) 320 return path 321 } 322 323 // setupBigFile creates a too big file for testing 324 // set with name=@filename. 325 func setupBigFile(c *gc.C, dir string) string { 326 ctx := coretesting.ContextForDir(c, dir) 327 path := ctx.AbsPath("big.txt") 328 file, err := os.Create(path) 329 c.Assert(err, jc.ErrorIsNil) 330 defer file.Close() 331 chunk := make([]byte, 1024) 332 for i := 0; i < cap(chunk); i++ { 333 chunk[i] = byte(i % 256) 334 } 335 for i := 0; i < 6000; i++ { 336 _, err = file.Write(chunk) 337 c.Assert(err, jc.ErrorIsNil) 338 } 339 return path 340 } 341 342 // setupConfigFile creates a configuration file for testing set 343 // with the --file argument specifying a configuration file. 344 func setupConfigFile(c *gc.C, dir string) string { 345 ctx := coretesting.ContextForDir(c, dir) 346 path := ctx.AbsPath("testconfig.yaml") 347 content := []byte(yamlConfigValue) 348 err := ioutil.WriteFile(path, content, 0666) 349 c.Assert(err, jc.ErrorIsNil) 350 return path 351 }