1package application_test
2
3import (
4	"errors"
5
6	"github.com/cloudfoundry/bosh-bootloader/application"
7	"github.com/cloudfoundry/bosh-bootloader/commands"
8	"github.com/cloudfoundry/bosh-bootloader/fakes"
9	"github.com/cloudfoundry/bosh-bootloader/storage"
10
11	. "github.com/onsi/ginkgo"
12	. "github.com/onsi/ginkgo/extensions/table"
13	. "github.com/onsi/gomega"
14)
15
16var _ = Describe("App", func() {
17	var (
18		app        application.App
19		helpCmd    *fakes.Command
20		versionCmd *fakes.Command
21		someCmd    *fakes.Command
22		errorCmd   *fakes.Command
23		usage      *fakes.Usage
24	)
25
26	var NewAppWithConfiguration = func(configuration application.Configuration) application.App {
27		return application.New(application.CommandSet{
28			"help":      helpCmd,
29			"version":   versionCmd,
30			"--version": versionCmd,
31			"some":      someCmd,
32			"error":     errorCmd,
33		},
34			configuration,
35			usage,
36		)
37	}
38
39	BeforeEach(func() {
40		helpCmd = &fakes.Command{}
41		versionCmd = &fakes.Command{}
42		errorCmd = &fakes.Command{}
43
44		someCmd = &fakes.Command{}
45		someCmd.ExecuteCall.PassState = true
46
47		usage = &fakes.Usage{}
48
49		app = NewAppWithConfiguration(application.Configuration{})
50	})
51
52	Describe("Run", func() {
53		Context("executing commands", func() {
54			It("executes the command with flags", func() {
55				app = NewAppWithConfiguration(application.Configuration{
56					Command: "some",
57					SubcommandFlags: []string{
58						"--first-subcommand-flag", "first-value",
59						"--second-subcommand-flag", "second-value",
60					},
61					Global: application.GlobalConfiguration{
62						StateDir: "some/state/dir",
63					},
64					State: storage.State{},
65				})
66
67				Expect(app.Run()).To(Succeed())
68
69				Expect(someCmd.ExecuteCall.CallCount).To(Equal(1))
70				Expect(someCmd.ExecuteCall.Receives.SubcommandFlags).To(Equal([]string{
71					"--first-subcommand-flag", "first-value",
72					"--second-subcommand-flag", "second-value",
73				}))
74			})
75		})
76
77		Context("when name is passed as a global flag", func() {
78			DescribeTable("propagates name to subcommand flags", func(command string) {
79				commandFake := &fakes.Command{}
80
81				app = application.New(
82					application.CommandSet{
83						command: commandFake,
84					},
85					application.Configuration{
86						Command: command,
87						SubcommandFlags: []string{
88							"--first-subcommand-flag", "first-value",
89							"--second-subcommand-flag", "second-value",
90						},
91						Global: application.GlobalConfiguration{
92							StateDir: "some/state/dir",
93							Name:     "some-env-name",
94						},
95						State: storage.State{},
96					},
97					usage)
98
99				Expect(app.Run()).To(Succeed())
100
101				Expect(commandFake.ExecuteCall.CallCount).To(Equal(1))
102				Expect(commandFake.ExecuteCall.Receives.SubcommandFlags).To(Equal([]string{
103					"--first-subcommand-flag", "first-value",
104					"--second-subcommand-flag", "second-value",
105					"--name", "some-env-name",
106				}))
107			},
108				Entry("when command is plan", "plan"),
109				Entry("when command is up", "up"),
110			)
111		})
112
113		Context("when subcommand flags contains help", func() {
114			DescribeTable("prints command specific usage when help subcommand flag is provided", func(helpFlag string) {
115				someCmd.UsageCall.Returns.Usage = "some usage message"
116
117				app = NewAppWithConfiguration(application.Configuration{
118					Command:         "some",
119					SubcommandFlags: []string{helpFlag},
120					ShowCommandHelp: true,
121				})
122
123				Expect(app.Run()).To(Succeed())
124				Expect(someCmd.UsageCall.CallCount).To(Equal(1))
125				Expect(usage.PrintCommandUsageCall.CallCount).To(Equal(1))
126				Expect(usage.PrintCommandUsageCall.Receives.Message).To(Equal("some usage message"))
127				Expect(usage.PrintCommandUsageCall.Receives.Command).To(Equal("some"))
128				Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
129			},
130				Entry("when --help is provided", "--help"),
131				Entry("when -h is provided", "-h"),
132			)
133		})
134
135		Context("when help is called with a command", func() {
136			It("prints the command specific help", func() {
137				someCmd.UsageCall.Returns.Usage = "some usage message"
138
139				app = NewAppWithConfiguration(application.Configuration{
140					Command:         "help",
141					SubcommandFlags: []string{"some"},
142				})
143
144				Expect(app.Run()).To(Succeed())
145				Expect(someCmd.UsageCall.CallCount).To(Equal(1))
146				Expect(usage.PrintCommandUsageCall.CallCount).To(Equal(1))
147				Expect(usage.PrintCommandUsageCall.Receives.Message).To(Equal("some usage message"))
148				Expect(usage.PrintCommandUsageCall.Receives.Command).To(Equal("some"))
149				Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
150			})
151
152			Context("failure cases", func() {
153				Context("when a invalid subcommand is passed", func() {
154					BeforeEach(func() {
155						app = NewAppWithConfiguration(application.Configuration{
156							Command:         "help",
157							SubcommandFlags: []string{"invalid-command"},
158						})
159					})
160
161					It("prints the usage", func() {
162						err := app.Run()
163						Expect(err).To(MatchError("unknown command: invalid-command"))
164						Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
165						Expect(usage.PrintCall.CallCount).To(Equal(1))
166					})
167				})
168			})
169		})
170
171		Context("when --version is the command", func() {
172			It("executes the command", func() {
173				app = NewAppWithConfiguration(application.Configuration{
174					Command: "--version",
175					SubcommandFlags: []string{
176						"--first-subcommand-flag", "first-value",
177						"--second-subcommand-flag", "second-value",
178					},
179				})
180
181				Expect(app.Run()).To(Succeed())
182
183				Expect(versionCmd.ExecuteCall.CallCount).To(Equal(1))
184				Expect(versionCmd.ExecuteCall.Receives.SubcommandFlags).To(Equal([]string{}))
185			})
186		})
187
188		Context("when subcommand flags contains version", func() {
189			DescribeTable("prints version when version subcommand flag is provided", func(versionFlag string) {
190				app = NewAppWithConfiguration(application.Configuration{
191					Command:         "some",
192					SubcommandFlags: []string{versionFlag},
193				})
194
195				Expect(app.Run()).To(Succeed())
196				Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
197				Expect(versionCmd.ExecuteCall.CallCount).To(Equal(1))
198				Expect(versionCmd.ExecuteCall.Receives.SubcommandFlags).To(Equal([]string{}))
199				Expect(versionCmd.ExecuteCall.Receives.State).To(Equal(storage.State{}))
200			},
201				Entry("when --version is provided", "--version"),
202				Entry("when -v is provided", "-v"),
203			)
204
205			Context("error cases", func() {
206				Context("when version command is not part of the command set", func() {
207					BeforeEach(func() {
208						app = application.New(application.CommandSet{
209							"some": someCmd,
210						}, application.Configuration{
211							Command:         "some",
212							SubcommandFlags: []string{"-v"},
213						}, usage)
214					})
215
216					It("returns an error", func() {
217						Expect(app.Run()).To(MatchError("unknown command: version"))
218					})
219				})
220			})
221		})
222
223		Context("error cases", func() {
224			Context("when a fast fail occurs", func() {
225				BeforeEach(func() {
226					someCmd.CheckFastFailsCall.Returns.Error = errors.New("fast failed command")
227				})
228
229				It("returns an error and does not execute the command", func() {
230					app = NewAppWithConfiguration(application.Configuration{
231						Command: "some",
232					})
233					err := app.Run()
234					Expect(someCmd.CheckFastFailsCall.CallCount).To(Equal(1))
235					Expect(err).To(MatchError("fast failed command"))
236					Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
237				})
238			})
239
240			Context("when a fast fail occurs, but indicates that its a success", func() {
241				BeforeEach(func() {
242					someCmd.CheckFastFailsCall.Returns.Error = commands.ExitSuccessfully{}
243				})
244
245				It("returns nil and doesn't execute the command", func() {
246					app = NewAppWithConfiguration(application.Configuration{
247						Command: "some",
248					})
249					err := app.Run()
250					Expect(someCmd.CheckFastFailsCall.CallCount).To(Equal(1))
251					Expect(err).NotTo(HaveOccurred())
252					Expect(someCmd.ExecuteCall.CallCount).To(Equal(0))
253				})
254			})
255
256			Context("when an unknown command is provided", func() {
257				It("prints usage and returns an error", func() {
258					app = NewAppWithConfiguration(application.Configuration{
259						Command: "some-unknown-command",
260					})
261					err := app.Run()
262					Expect(err).To(MatchError("unknown command: some-unknown-command"))
263					Expect(usage.PrintCall.CallCount).To(Equal(1))
264				})
265			})
266
267			Context("when the command fails to execute", func() {
268				It("returns an error", func() {
269					errorCmd.ExecuteCall.Returns.Error = errors.New("error executing command")
270					app = NewAppWithConfiguration(application.Configuration{
271						Command: "error",
272						Global: application.GlobalConfiguration{
273							Debug: true,
274						},
275					})
276					Expect(app.Run()).To(MatchError("error executing command"))
277				})
278			})
279		})
280	})
281})
282