1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
3
package application_test
13
jc "github.com/juju/testing/checkers"
14
"github.com/juju/utils"
15
gc "gopkg.in/check.v1"
16
goyaml "gopkg.in/yaml.v2"
18
"github.com/juju/juju/apiserver/common"
19
"github.com/juju/juju/cmd/juju/application"
20
coretesting "github.com/juju/juju/testing"
23
type configCommandSuite struct {
24
coretesting.FakeJujuXDGDataHomeSuite
26
fake *fakeApplicationAPI
30
_ = gc.Suite(&configCommandSuite{})
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"
37
var getTests = []struct {
39
expected map[string]interface{}
43
map[string]interface{}{
44
"application": "dummy-application",
46
"settings": map[string]interface{}{
47
"title": map[string]interface{}{
48
"description": "Specifies title",
50
"value": "Nearly There",
52
"skill-level": map[string]interface{}{
53
"description": "Specifies skill-level",
57
"username": map[string]interface{}{
58
"description": "Specifies username",
62
"outlook": map[string]interface{}{
63
"description": "Specifies outlook",
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",
78
"username": "admin001",
81
s.FakeJujuXDGDataHomeSuite.SetUpTest(c)
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)
92
func (s *configCommandSuite) TestGetCommandInit(c *gc.C) {
94
err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{})
95
c.Assert(err, gc.ErrorMatches, "no application name specified")
98
func (s *configCommandSuite) TestGetCommandInitWithApplication(c *gc.C) {
99
err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app"})
101
c.Assert(err, jc.ErrorIsNil)
104
func (s *configCommandSuite) TestGetCommandInitWithKey(c *gc.C) {
105
err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{"app", "key"})
107
c.Assert(err, jc.ErrorIsNil)
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")
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)
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)
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")
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, "")
153
func (s *configCommandSuite) TestSetCommandInit(c *gc.C) {
155
err := coretesting.InitCommand(application.NewConfigCommandForTest(s.fake), []string{})
156
c.Assert(err, gc.ErrorMatches, "no application name specified")
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")
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")
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")
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")
176
func (s *configCommandSuite) TestSetOptionSuccess(c *gc.C) {
177
s.assertSetSuccess(c, s.dir, []string{
179
"outlook=hello@world.tld",
180
}, map[string]interface{}{
182
"outlook": "hello@world.tld",
184
s.assertSetSuccess(c, s.dir, []string{
185
"username=hello=foo",
186
}, map[string]interface{}{
187
"username": "hello=foo",
188
"outlook": "hello@world.tld",
190
s.assertSetSuccess(c, s.dir, []string{
191
"username=@valid.txt",
192
}, map[string]interface{}{
193
"username": validSetTestValue,
194
"outlook": "hello@world.tld",
196
s.assertSetSuccess(c, s.dir, []string{
198
}, map[string]interface{}{
200
"outlook": "hello@world.tld",
204
func (s *configCommandSuite) TestSetSameValue(c *gc.C) {
205
s.assertSetSuccess(c, s.dir, []string{
207
"outlook=hello@world.tld",
208
}, map[string]interface{}{
210
"outlook": "hello@world.tld",
212
s.assertSetWarning(c, s.dir, []string{
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\"")
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{
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")
236
func (s *configCommandSuite) TestSetConfig(c *gc.C) {
237
s.assertSetFail(c, s.dir, []string{
240
}, "error.* "+utils.NoSuchFileErrRegexp+"\n")
242
ctx := coretesting.ContextForDir(c, s.dir)
243
code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{
248
c.Check(code, gc.Equals, 0)
249
c.Check(s.fake.config, gc.Equals, yamlConfigValue)
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{
261
c.Check(code, gc.Equals, 0)
262
c.Check(s.fake.config, jc.DeepEquals, "settings:\n username:\n value: world\n")
265
func (s *configCommandSuite) TestResetConfigToDefault(c *gc.C) {
266
s.fake = &fakeApplicationAPI{name: "dummy-application", values: map[string]interface{}{
269
s.assertSetSuccess(c, s.dir, []string{
272
}, make(map[string]interface{}))
275
func (s *configCommandSuite) TestBlockSetConfig(c *gc.C) {
277
s.fake.err = common.OperationBlockedError("TestBlockSetConfig")
278
ctx := coretesting.ContextForDir(c, s.dir)
279
code := cmd.Main(application.NewConfigCommandForTest(s.fake), ctx, []string{
283
c.Check(code, gc.Equals, 1)
285
stripped := strings.Replace(c.GetTestLog(), "\n", "", -1)
286
c.Check(stripped, gc.Matches, ".*TestBlockSetConfig.*")
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)
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)
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)
309
c.Assert(strings.Replace(c.GetTestLog(), "\n", " ", -1), gc.Matches, ".*WARNING.*"+w+".*")
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)
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)
331
chunk := make([]byte, 1024)
332
for i := 0; i < cap(chunk); i++ {
333
chunk[i] = byte(i % 256)
335
for i := 0; i < 6000; i++ {
336
_, err = file.Write(chunk)
337
c.Assert(err, jc.ErrorIsNil)
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)