diff --git a/cli-plugin-recorder b/cli-plugin-recorder new file mode 100755 index 0000000..f0db00d Binary files /dev/null and b/cli-plugin-recorder differ diff --git a/data/data.go b/data/data.go index 8700128..132644c 100644 --- a/data/data.go +++ b/data/data.go @@ -10,6 +10,7 @@ import ( "strings" ) +//go:generate counterfeiter -o data_fakes/fake_data.go . CmdSetData type CmdSetData interface { GetCmdSet(string) []string SaveCmdSet(string, []string) @@ -97,12 +98,14 @@ func (c *cmdSetData) SaveCmdSet(cmdsetName string, cmdset []string) { fp := getFilePath() _, err := os.Stat(fp) if err != nil && !os.IsExist(err) { + err = nil f, err = os.Create(fp) } else { f, err = os.OpenFile(fp, os.O_RDWR|os.O_APPEND, 0660) } + if err != nil { - quitWithError("Error writing to file: ", err) + quitWithError("Error opening file: ", err) } defer f.Close() @@ -124,8 +127,6 @@ func (c *cmdSetData) ClearCmdSets() { fmt.Println("Error removing file: ", err) os.Exit(1) } - - fmt.Println("All record command sets has be removed") } func (c *cmdSetData) DeleteCmdSet(cmdsetName string) { diff --git a/data/data_fakes/fake_data.go b/data/data_fakes/fake_data.go new file mode 100644 index 0000000..b7b7a1f --- /dev/null +++ b/data/data_fakes/fake_data.go @@ -0,0 +1,199 @@ +// This file was generated by counterfeiter +package data_fakes + +import ( + "sync" + + "github.com/simonleung8/cli-plugin-recorder/data" +) + +type FakeCmdSetData struct { + GetCmdSetStub func(string) []string + getCmdSetMutex sync.RWMutex + getCmdSetArgsForCall []struct { + arg1 string + } + getCmdSetReturns struct { + result1 []string + } + SaveCmdSetStub func(string, []string) + saveCmdSetMutex sync.RWMutex + saveCmdSetArgsForCall []struct { + arg1 string + arg2 []string + } + DeleteCmdSetStub func(string) + deleteCmdSetMutex sync.RWMutex + deleteCmdSetArgsForCall []struct { + arg1 string + } + IsCmdExistStub func(t string) bool + isCmdExistMutex sync.RWMutex + isCmdExistArgsForCall []struct { + t string + } + isCmdExistReturns struct { + result1 bool + } + ListCmdSetNamesStub func() []string + listCmdSetNamesMutex sync.RWMutex + listCmdSetNamesArgsForCall []struct{} + listCmdSetNamesReturns struct { + result1 []string + } + ClearCmdSetsStub func() + clearCmdSetsMutex sync.RWMutex + clearCmdSetsArgsForCall []struct{} +} + +func (fake *FakeCmdSetData) GetCmdSet(arg1 string) []string { + fake.getCmdSetMutex.Lock() + fake.getCmdSetArgsForCall = append(fake.getCmdSetArgsForCall, struct { + arg1 string + }{arg1}) + fake.getCmdSetMutex.Unlock() + if fake.GetCmdSetStub != nil { + return fake.GetCmdSetStub(arg1) + } else { + return fake.getCmdSetReturns.result1 + } +} + +func (fake *FakeCmdSetData) GetCmdSetCallCount() int { + fake.getCmdSetMutex.RLock() + defer fake.getCmdSetMutex.RUnlock() + return len(fake.getCmdSetArgsForCall) +} + +func (fake *FakeCmdSetData) GetCmdSetArgsForCall(i int) string { + fake.getCmdSetMutex.RLock() + defer fake.getCmdSetMutex.RUnlock() + return fake.getCmdSetArgsForCall[i].arg1 +} + +func (fake *FakeCmdSetData) GetCmdSetReturns(result1 []string) { + fake.GetCmdSetStub = nil + fake.getCmdSetReturns = struct { + result1 []string + }{result1} +} + +func (fake *FakeCmdSetData) SaveCmdSet(arg1 string, arg2 []string) { + fake.saveCmdSetMutex.Lock() + fake.saveCmdSetArgsForCall = append(fake.saveCmdSetArgsForCall, struct { + arg1 string + arg2 []string + }{arg1, arg2}) + fake.saveCmdSetMutex.Unlock() + if fake.SaveCmdSetStub != nil { + fake.SaveCmdSetStub(arg1, arg2) + } +} + +func (fake *FakeCmdSetData) SaveCmdSetCallCount() int { + fake.saveCmdSetMutex.RLock() + defer fake.saveCmdSetMutex.RUnlock() + return len(fake.saveCmdSetArgsForCall) +} + +func (fake *FakeCmdSetData) SaveCmdSetArgsForCall(i int) (string, []string) { + fake.saveCmdSetMutex.RLock() + defer fake.saveCmdSetMutex.RUnlock() + return fake.saveCmdSetArgsForCall[i].arg1, fake.saveCmdSetArgsForCall[i].arg2 +} + +func (fake *FakeCmdSetData) DeleteCmdSet(arg1 string) { + fake.deleteCmdSetMutex.Lock() + fake.deleteCmdSetArgsForCall = append(fake.deleteCmdSetArgsForCall, struct { + arg1 string + }{arg1}) + fake.deleteCmdSetMutex.Unlock() + if fake.DeleteCmdSetStub != nil { + fake.DeleteCmdSetStub(arg1) + } +} + +func (fake *FakeCmdSetData) DeleteCmdSetCallCount() int { + fake.deleteCmdSetMutex.RLock() + defer fake.deleteCmdSetMutex.RUnlock() + return len(fake.deleteCmdSetArgsForCall) +} + +func (fake *FakeCmdSetData) DeleteCmdSetArgsForCall(i int) string { + fake.deleteCmdSetMutex.RLock() + defer fake.deleteCmdSetMutex.RUnlock() + return fake.deleteCmdSetArgsForCall[i].arg1 +} + +func (fake *FakeCmdSetData) IsCmdExist(t string) bool { + fake.isCmdExistMutex.Lock() + fake.isCmdExistArgsForCall = append(fake.isCmdExistArgsForCall, struct { + t string + }{t}) + fake.isCmdExistMutex.Unlock() + if fake.IsCmdExistStub != nil { + return fake.IsCmdExistStub(t) + } else { + return fake.isCmdExistReturns.result1 + } +} + +func (fake *FakeCmdSetData) IsCmdExistCallCount() int { + fake.isCmdExistMutex.RLock() + defer fake.isCmdExistMutex.RUnlock() + return len(fake.isCmdExistArgsForCall) +} + +func (fake *FakeCmdSetData) IsCmdExistArgsForCall(i int) string { + fake.isCmdExistMutex.RLock() + defer fake.isCmdExistMutex.RUnlock() + return fake.isCmdExistArgsForCall[i].t +} + +func (fake *FakeCmdSetData) IsCmdExistReturns(result1 bool) { + fake.IsCmdExistStub = nil + fake.isCmdExistReturns = struct { + result1 bool + }{result1} +} + +func (fake *FakeCmdSetData) ListCmdSetNames() []string { + fake.listCmdSetNamesMutex.Lock() + fake.listCmdSetNamesArgsForCall = append(fake.listCmdSetNamesArgsForCall, struct{}{}) + fake.listCmdSetNamesMutex.Unlock() + if fake.ListCmdSetNamesStub != nil { + return fake.ListCmdSetNamesStub() + } else { + return fake.listCmdSetNamesReturns.result1 + } +} + +func (fake *FakeCmdSetData) ListCmdSetNamesCallCount() int { + fake.listCmdSetNamesMutex.RLock() + defer fake.listCmdSetNamesMutex.RUnlock() + return len(fake.listCmdSetNamesArgsForCall) +} + +func (fake *FakeCmdSetData) ListCmdSetNamesReturns(result1 []string) { + fake.ListCmdSetNamesStub = nil + fake.listCmdSetNamesReturns = struct { + result1 []string + }{result1} +} + +func (fake *FakeCmdSetData) ClearCmdSets() { + fake.clearCmdSetsMutex.Lock() + fake.clearCmdSetsArgsForCall = append(fake.clearCmdSetsArgsForCall, struct{}{}) + fake.clearCmdSetsMutex.Unlock() + if fake.ClearCmdSetsStub != nil { + fake.ClearCmdSetsStub() + } +} + +func (fake *FakeCmdSetData) ClearCmdSetsCallCount() int { + fake.clearCmdSetsMutex.RLock() + defer fake.clearCmdSetsMutex.RUnlock() + return len(fake.clearCmdSetsArgsForCall) +} + +var _ data.CmdSetData = new(FakeCmdSetData) diff --git a/data/data_suite_test.go b/data/data_suite_test.go new file mode 100644 index 0000000..b521810 --- /dev/null +++ b/data/data_suite_test.go @@ -0,0 +1,13 @@ +package data_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestData(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Data Suite") +} diff --git a/data/data_test.go b/data/data_test.go new file mode 100644 index 0000000..9defc4b --- /dev/null +++ b/data/data_test.go @@ -0,0 +1,204 @@ +package data_test + +import ( + "io/ioutil" + "os" + "path/filepath" + + "github.com/simonleung8/cli-plugin-recorder/data" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Data", func() { + var ( + dataStore data.CmdSetData + ) + + BeforeEach(func() { + wd, err := os.Getwd() + Ω(err).ToNot(HaveOccurred()) + os.Setenv("CF_PLUGIN_HOME", filepath.Join(wd, "test_fixtures")) + + dataStore = data.NewCmdSetData() + }) + + Describe("GetCmdSet()", func() { + It("returns a list of recorded commands in the command set", func() { + Ω(dataStore.GetCmdSet("cmdSet1")).To(Equal([]string{"api api.sample.com", "auth admin admin", "create-org test-org"})) + }) + + It("returns empty []string when data file is not found", func() { + os.Setenv("CF_PLUGIN_HOME", "path_to_no_where/") + + cmdSet := dataStore.GetCmdSet("cmdSet1") + Ω(cmdSet).To(Equal([]string{})) + }) + }) + + Describe("ListCmdSetNames()", func() { + It("returns a list of recorded commands set names", func() { + Ω(dataStore.ListCmdSetNames()).To(Equal([]string{"cmdSet1", "cmdSet2"})) + }) + + It("returns empty []string when data file is not found", func() { + os.Setenv("CF_PLUGIN_HOME", "path_to_no_where/") + + cmdSet := dataStore.ListCmdSetNames() + Ω(cmdSet).To(Equal([]string{})) + }) + }) + + Describe("IsCmdExist()", func() { + It("returns true if a command set exists", func() { + Ω(dataStore.IsCmdExist("cmdSet1")).To(BeTrue()) + }) + + It("returns false if a command set exists", func() { + Ω(dataStore.IsCmdExist("no_cmd_set")).To(BeFalse()) + }) + }) + + Describe("SaveCmdSet()", func() { + var ( + wd string + err error + cmdSetName string + cmds []string + CF_PLUGIN_HOME string + ) + + BeforeEach(func() { + wd, err = os.Getwd() + Ω(err).ToNot(HaveOccurred()) + + CF_PLUGIN_HOME = filepath.Join(wd, "test_fixtures", "tmp") + os.Setenv("CF_PLUGIN_HOME", CF_PLUGIN_HOME) + + err = os.MkdirAll(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins"), 0700) + Ω(err).ToNot(HaveOccurred()) + + cmdSetName = "sample_set" + cmds = []string{"cmd1", "cmd2"} + }) + + AfterEach(func() { + err = os.RemoveAll(os.Getenv("CF_PLUGIN_HOME")) + Ω(err).ToNot(HaveOccurred()) + }) + + Context("When data file does not exist", func() { + It("creates a file called CmdSetRecords", func() { + _, err = os.Stat(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(os.IsExist(err)).To(BeFalse()) + + dataStore.SaveCmdSet(cmdSetName, cmds) + _, err = os.Stat(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(err).ToNot(HaveOccurred()) + }) + + It("writes the recorded command set to file", func() { + dataStore.SaveCmdSet(cmdSetName, cmds) + + f, err := os.Open(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(err).ToNot(HaveOccurred()) + + contents, err := ioutil.ReadAll(f) + Ω(err).ToNot(HaveOccurred()) + + Ω(string(contents)).To(ContainSubstring("--sample_set")) + Ω(string(contents)).To(ContainSubstring(" cmd1")) + Ω(string(contents)).To(ContainSubstring(" cmd2")) + }) + }) + + Context("When data file already exists", func() { + BeforeEach(func() { + dataStore.SaveCmdSet(cmdSetName, cmds) + }) + + It("appends the recorded command set to file", func() { + dataStore.SaveCmdSet("sample_set2", []string{"cmda", "cmdb"}) + + f, err := os.Open(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(err).ToNot(HaveOccurred()) + + contents, err := ioutil.ReadAll(f) + Ω(err).ToNot(HaveOccurred()) + + Ω(string(contents)).To(ContainSubstring("--sample_set")) + Ω(string(contents)).To(ContainSubstring(" cmd1")) + Ω(string(contents)).To(ContainSubstring(" cmd2")) + Ω(string(contents)).To(ContainSubstring("--sample_set2")) + Ω(string(contents)).To(ContainSubstring(" cmda")) + Ω(string(contents)).To(ContainSubstring(" cmdb")) + }) + }) + }) + + Describe("Removing data", func() { + var ( + wd string + err error + CF_PLUGIN_HOME string + ) + + BeforeEach(func() { + wd, err = os.Getwd() + Ω(err).ToNot(HaveOccurred()) + + CF_PLUGIN_HOME = filepath.Join(wd, "test_fixtures", "tmp") + os.Setenv("CF_PLUGIN_HOME", CF_PLUGIN_HOME) + + err = os.MkdirAll(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins"), 0700) + Ω(err).ToNot(HaveOccurred()) + + dataStore.SaveCmdSet("sample_set", []string{"cmd1", "cmd2"}) + }) + + AfterEach(func() { + os.RemoveAll(os.Getenv("CF_PLUGIN_HOME")) + }) + + Describe("ClearCmdSet()", func() { + It("deletes the entire data file", func() { + _, err = os.Stat(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(err).ToNot(HaveOccurred()) + + dataStore.ClearCmdSets() + + _, err = os.Stat(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(os.IsExist(err)).To(BeFalse()) + }) + }) + + Describe("DeleteCmdSet()", func() { + BeforeEach(func() { + dataStore.SaveCmdSet("sample_set2", []string{"cmda", "cmdb"}) + dataStore.SaveCmdSet("sample_set3", []string{"cmdI", "cmdII"}) + }) + + It("removes the recorded command set from data file", func() { + dataStore.DeleteCmdSet("sample_set2") + + f, err := os.Open(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins", "CmdSetRecords")) + Ω(err).ToNot(HaveOccurred()) + + contents, err := ioutil.ReadAll(f) + Ω(err).ToNot(HaveOccurred()) + + Ω(string(contents)).To(ContainSubstring("--sample_set")) + Ω(string(contents)).To(ContainSubstring(" cmd1")) + Ω(string(contents)).To(ContainSubstring(" cmd2")) + Ω(string(contents)).ToNot(ContainSubstring("--sample_set2")) + Ω(string(contents)).ToNot(ContainSubstring(" cmda")) + Ω(string(contents)).ToNot(ContainSubstring(" cmdb")) + Ω(string(contents)).To(ContainSubstring("--sample_set3")) + Ω(string(contents)).To(ContainSubstring(" cmdI")) + Ω(string(contents)).To(ContainSubstring(" cmdII")) + }) + + }) + }) +}) diff --git a/data/test_fixtures/.cf/plugins/CmdSetRecords b/data/test_fixtures/.cf/plugins/CmdSetRecords new file mode 100644 index 0000000..f87a822 --- /dev/null +++ b/data/test_fixtures/.cf/plugins/CmdSetRecords @@ -0,0 +1,7 @@ +--cmdSet1 + api api.sample.com + auth admin admin + create-org test-org +--cmdSet2 + target -o public -s public + apps diff --git a/main.go b/main.go index cf51f2c..5c70fe3 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "os" "github.com/cloudfoundry/cli/plugin" + "github.com/simonleung8/cli-plugin-recorder/data" "github.com/simonleung8/cli-plugin-recorder/record" "github.com/simonleung8/cli-plugin-recorder/replay" @@ -19,7 +20,7 @@ func (c *CLI_Recorder) GetMetadata() plugin.PluginMetadata { Version: plugin.VersionType{ Major: 1, Minor: 0, - Build: 1, + Build: 2, }, Commands: []plugin.Command{ { @@ -29,8 +30,8 @@ func (c *CLI_Recorder) GetMetadata() plugin.PluginMetadata { Usage: `record COMMAND_SET_NAME | OPTIONS OPTIONS: - -n list all commands within a set - -d delete a command set + -n list all commands within a set (e.g. -n COMMAND_SET_NAME) + -d delete a command set (e.g. -d COMMAND_SET_NAME) --list, -l list all recorded command sets --clear, -c clear all recorded commands `, @@ -41,7 +42,11 @@ OPTIONS: Alias: "rp", HelpText: "replay a set of recorded CLI commands", UsageDetails: plugin.Usage{ - Usage: "replay COMMAND_SET_NAME", + Usage: `replay COMMAND_SET_NAME | OPTIONS + +OPTIONS: + --list, -l list all recorded command sets +`, }, }, }, @@ -53,7 +58,9 @@ func main() { } func (c *CLI_Recorder) Run(cliConnection plugin.CliConnection, args []string) { - if args[0] == "record" { + if args[0] == "CLI-MESSAGE-UNINSTALL" { + + } else if args[0] == "record" { fc := flags.New() fc.NewBoolFlag("list", "l", "list all recorded command sets") fc.NewBoolFlag("clear", "c", "clear all recorded command sets") @@ -65,19 +72,32 @@ func (c *CLI_Recorder) Run(cliConnection plugin.CliConnection, args []string) { os.Exit(1) } - runRecord(cliConnection, fc) + c.runRecord(cliConnection, fc) + } else if args[0] == "replay" { - if len(args) > 1 { - p := replay.NewReplayCmds(cliConnection, args[1:]...) + fc := flags.New() + fc.NewBoolFlag("list", "l", "list all recorded command sets") + err := fc.Parse(args[1:]...) + if err != nil { + fmt.Println("Error:", err) + os.Exit(1) + } + + if fc.Bool("l") || fc.Bool("list") { + r := record.NewRecordCmd(cliConnection, data.NewCmdSetData(), os.Stdin) + r.ListCmdSets() + } else if len(args) > 1 { + p := replay.NewReplayCmds(cliConnection, data.NewCmdSetData(), args[1:]...) p.Run() } else { fmt.Println("Provide the recorded command set name to playback") + fmt.Printf("\nUSAGE:\n %s\n", c.GetMetadata().Commands[1].UsageDetails.Usage) } } } -func runRecord(cliConnection plugin.CliConnection, fc flags.FlagContext) { - r := record.NewRecordCmd(cliConnection) +func (c *CLI_Recorder) runRecord(cliConnection plugin.CliConnection, fc flags.FlagContext) { + r := record.NewRecordCmd(cliConnection, data.NewCmdSetData(), os.Stdin) if fc.Bool("l") || fc.Bool("list") { r.ListCmdSets() } else if fc.Bool("clear") { @@ -89,6 +109,7 @@ func runRecord(cliConnection plugin.CliConnection, fc flags.FlagContext) { } else if len(fc.Args()) == 1 { r.Record(fc.Args()[0]) } else { - fmt.Println("Provide the recorded command set name to playback") + fmt.Println("Provide command set name for recording") + fmt.Printf("\nUSAGE:\n %s\n", c.GetMetadata().Commands[0].UsageDetails.Usage) } } diff --git a/main_suite_test.go b/main_suite_test.go new file mode 100644 index 0000000..3bd9158 --- /dev/null +++ b/main_suite_test.go @@ -0,0 +1,18 @@ +package main_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/cloudfoundry/cli/testhelpers/plugin_builder" + + "testing" +) + +func TestCliPluginRecorder(t *testing.T) { + RegisterFailHandler(Fail) + + plugin_builder.BuildTestBinary("", "main") + + RunSpecs(t, "CliPluginRecorder Suite") +} diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..214364d --- /dev/null +++ b/main_test.go @@ -0,0 +1,179 @@ +package main_test + +import ( + "os" + "os/exec" + "path/filepath" + + "github.com/cloudfoundry/cli/testhelpers/rpc_server" + fake_rpc_handlers "github.com/cloudfoundry/cli/testhelpers/rpc_server/fakes" + "github.com/simonleung8/cli-plugin-recorder/data" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/onsi/gomega/gbytes" + "github.com/onsi/gomega/gexec" +) + +var _ = Describe("CliPluginRecorder", func() { + var ( + validPluginPath = "./main.exe" + CF_PLUGIN_HOME string + rpcHandlers *fake_rpc_handlers.FakeHandlers + ts *test_rpc_server.TestServer + ) + + BeforeEach(func() { + wd, err := os.Getwd() + Ω(err).ToNot(HaveOccurred()) + + CF_PLUGIN_HOME = filepath.Join(wd, "data", "test_fixtures", "tmp") + os.Setenv("CF_PLUGIN_HOME", CF_PLUGIN_HOME) + + err = os.MkdirAll(filepath.Join(CF_PLUGIN_HOME, ".cf", "plugins"), 0700) + Ω(err).ToNot(HaveOccurred()) + + dataStore := data.NewCmdSetData() + dataStore.SaveCmdSet("test_set1", []string{"cmd1", "cmd2"}) + dataStore.SaveCmdSet("test_set2", []string{"cmda", "cmdb"}) + + rpcHandlers = &fake_rpc_handlers.FakeHandlers{} + ts, err = test_rpc_server.NewTestRpcServer(rpcHandlers) + Expect(err).NotTo(HaveOccurred()) + + err = ts.Start() + Expect(err).NotTo(HaveOccurred()) + + //set rpc.CallCoreCommand to a successful call + rpcHandlers.CallCoreCommandStub = func(_ []string, retVal *bool) error { + *retVal = true + return nil + } + + //set rpc.GetOutputAndReset to return empty string; this is used by CliCommand()/CliWithoutTerminalOutput() + rpcHandlers.GetOutputAndResetStub = func(_ bool, retVal *[]string) error { + *retVal = []string{"{}"} + return nil + } + }) + + AfterEach(func() { + err := os.RemoveAll(CF_PLUGIN_HOME) + Ω(err).ToNot(HaveOccurred()) + + ts.Stop() + }) + + AfterSuite(func() { + err := os.RemoveAll("./main.exe") + Ω(err).ToNot(HaveOccurred()) + }) + + Describe("Command Record", func() { + It("complains about invalid flag", func() { + args := []string{ts.Port(), "record", "--bad-flag"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Ω(session).To(gbytes.Say("Invalid flag: --bad-flag")) + }) + + It("needs at least 1 argument", func() { + args := []string{ts.Port(), "record"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Ω(session).To(gbytes.Say("Provide command set name for recording")) + }) + + It("calls data.Record() to record commands", func() { + args := []string{ts.Port(), "record", "test_command"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Ω(session).To(gbytes.Say("Please start entering CF commands")) + }) + + Context("--list or -l flag", func() { + It("lists all recorded command sets", func() { + wd, _ := os.Getwd() + os.Setenv("CF_PLUGIN_HOME", filepath.Join(wd, "data", "test_fixtures")) + + args := []string{ts.Port(), "record", "--list"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Eventually(session).Should(gbytes.Say("The following command sets are available")) + Eventually(session).Should(gbytes.Say("=============================")) + Eventually(session).Should(gbytes.Say("cmdSet1")) + Eventually(session).Should(gbytes.Say("cmdSet2")) + }) + }) + + Context("-n flag", func() { + It("lists all commands within a command sets", func() { + args := []string{ts.Port(), "record", "-n", "test_set1"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Eventually(session).Should(gbytes.Say("The following commands are in test_set1")) + Eventually(session).Should(gbytes.Say("cmd1")) + Eventually(session).Should(gbytes.Say("cmd2")) + }) + }) + + Context("--clear or -c flag", func() { + It("clears the entire data file by calling data.ClearCmdSet()", func() { + args := []string{ts.Port(), "record", "--clear"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Eventually(session).Should(gbytes.Say("WARNING: You are about to delete all recorded command sets")) + }) + }) + }) + + Describe("Command Replay", func() { + It("needs at least 1 argument", func() { + args := []string{ts.Port(), "replay"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Ω(session).To(gbytes.Say("Provide the recorded command set name to playback")) + }) + + It("calls replay.Run() to replay commands", func() { + args := []string{ts.Port(), "replay", "bad_command_set"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Eventually(session).Should(gbytes.Say("Command set bad_command_set not found")) + }) + }) + + Context("--list or -l flag", func() { + It("lists all recorded command sets", func() { + wd, _ := os.Getwd() + os.Setenv("CF_PLUGIN_HOME", filepath.Join(wd, "data", "test_fixtures")) + + args := []string{ts.Port(), "replay", "--list"} + session, err := gexec.Start(exec.Command(validPluginPath, args...), GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + session.Wait() + Eventually(session).Should(gbytes.Say("The following command sets are available")) + Eventually(session).Should(gbytes.Say("=============================")) + Eventually(session).Should(gbytes.Say("cmdSet1")) + Eventually(session).Should(gbytes.Say("cmdSet2")) + }) + }) +}) diff --git a/record/record.go b/record/record.go index aad0bcf..18ef984 100644 --- a/record/record.go +++ b/record/record.go @@ -19,14 +19,18 @@ type RecordCmd interface { } type recordCmd struct { - name string - cli plugin.CliConnection - cmdset []string + name string + cli plugin.CliConnection + cmdset []string + cmdSetData data.CmdSetData + inputStream *os.File } -func NewRecordCmd(cli plugin.CliConnection) RecordCmd { +func NewRecordCmd(cli plugin.CliConnection, cmdSetData data.CmdSetData, inputStream *os.File) RecordCmd { return &recordCmd{ - cli: cli, + cli: cli, + cmdSetData: cmdSetData, + inputStream: inputStream, } } @@ -34,8 +38,7 @@ func (r *recordCmd) ListCmdSets() { fmt.Println("The following command sets are available:") fmt.Println("=========================================") - d := data.NewCmdSetData() - cmdsets := d.ListCmdSetNames() + cmdsets := r.cmdSetData.ListCmdSetNames() for _, name := range cmdsets { fmt.Println(name) @@ -47,8 +50,7 @@ func (r *recordCmd) ListCmds(cmdset string) { fmt.Printf("The following commands are in %s:\n", cmdset) fmt.Println("=========================================") - d := data.NewCmdSetData() - cmds := d.GetCmdSet(cmdset) + cmds := r.cmdSetData.GetCmdSet(cmdset) for _, cmd := range cmds { fmt.Println(cmd) @@ -59,11 +61,10 @@ func (r *recordCmd) ListCmds(cmdset string) { func (r *recordCmd) Record(cmdsetName string) { var cmd string var err error - d := data.NewCmdSetData() r.name = strings.TrimSpace(cmdsetName) - if d.IsCmdExist(r.name) { + if r.cmdSetData.IsCmdExist(r.name) { fmt.Printf("\nThe name %s already exists, please use another ...\n\n", r.name) return } @@ -78,7 +79,7 @@ type 'quit' to quit recording without saving for { fmt.Printf("\n(recording) >> ") - in := bufio.NewReader(os.Stdin) + in := bufio.NewReader(r.inputStream) cmd, err = in.ReadString('\n') if err != nil { fmt.Println("Error: ", err) @@ -102,17 +103,27 @@ type 'quit' to quit recording without saving } } - d.SaveCmdSet(r.name, r.cmdset) + r.cmdSetData.SaveCmdSet(r.name, r.cmdset) } func (r *recordCmd) DeleteCmdSet(cmdset string) { - d := data.NewCmdSetData() - d.DeleteCmdSet(cmdset) + r.cmdSetData.DeleteCmdSet(cmdset) } func (r *recordCmd) ClearCmdSets() { - d := data.NewCmdSetData() - d.ClearCmdSets() + fmt.Print("WARNING: You are about to delete all recorded command sets (y or n): ") + in := bufio.NewReader(r.inputStream) + response, err := in.ReadString('\n') + if err != nil { + fmt.Println("Error: ", err) + } + + if strings.TrimSpace(response) == "y" { + r.cmdSetData.ClearCmdSets() + fmt.Println("All recorded command sets has been removed") + } else { + fmt.Println("Action aborted.") + } } func validCfCmd(cmd string) bool { diff --git a/record/record_suite_test.go b/record/record_suite_test.go new file mode 100644 index 0000000..67823ec --- /dev/null +++ b/record/record_suite_test.go @@ -0,0 +1,13 @@ +package record_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestRecord(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Record Suite") +} diff --git a/record/record_test.go b/record/record_test.go new file mode 100644 index 0000000..ffd98b4 --- /dev/null +++ b/record/record_test.go @@ -0,0 +1,208 @@ +package record_test + +import ( + "os" + "time" + + "github.com/cloudfoundry/cli/plugin/fakes" + io_helpers "github.com/cloudfoundry/cli/testhelpers/io" + + "github.com/simonleung8/cli-plugin-recorder/data/data_fakes" + + "github.com/simonleung8/cli-plugin-recorder/record" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Record Package", func() { + var ( + fakeData *data_fakes.FakeCmdSetData + fakeCliConnection *fakes.FakeCliConnection + recorder record.RecordCmd + rFile *os.File + wFile *os.File + err error + ) + + BeforeEach(func() { + fakeCliConnection = &fakes.FakeCliConnection{} + fakeData = &data_fakes.FakeCmdSetData{} + rFile, wFile, err = os.Pipe() + Ω(err).ToNot(HaveOccurred()) + }) + + AfterEach(func() { + rFile.Close() + wFile.Close() + }) + + Describe("Record()", func() { + It("warns user about an existing command set name", func() { + fakeData.IsCmdExistReturns(true) + + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + output := io_helpers.CaptureOutput(func() { + recorder.Record("cmdSet-Name1") + }) + + Ω(output[1]).Should(ContainSubstring("The name cmdSet-Name1 already exists")) + }) + + It("uses the data store to save the recorded command set", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + go io_helpers.CaptureOutput(func() { + recorder.Record("cmdSet-Name2") + }) + + wFile.WriteString("cf api blah\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("cf target\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("stop\n") + time.Sleep(100 * time.Millisecond) + Ω(fakeData.SaveCmdSetCallCount()).To(Equal(1)) + + cmdSetName, cmds := fakeData.SaveCmdSetArgsForCall(0) + Ω(cmdSetName).To(Equal("cmdSet-Name2")) + Ω(cmds).To(Equal([]string{"api blah", "target"})) + }) + + It("trims spaces for command set name and commands before saving", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + go io_helpers.CaptureOutput(func() { + recorder.Record(" cmdSet-Name2 ") + }) + + wFile.WriteString(" cf api blah \n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("stop\n") + time.Sleep(100 * time.Millisecond) + Ω(fakeData.SaveCmdSetCallCount()).To(Equal(1)) + + cmdSetName, cmds := fakeData.SaveCmdSetArgsForCall(0) + Ω(cmdSetName).To(Equal("cmdSet-Name2")) + Ω(cmds).To(Equal([]string{"api blah"})) + }) + + It("abandon recording when user input 'quit'", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + go io_helpers.CaptureOutput(func() { + recorder.Record("cmdSet-Name2") + }) + + wFile.WriteString("cf api blah\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("quit\n") + time.Sleep(100 * time.Millisecond) + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(1)) + Ω(fakeData.SaveCmdSetCallCount()).To(Equal(0)) + }) + + It("does not invoke invalid command without 'cf' prefix", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + go io_helpers.CaptureOutput(func() { + recorder.Record("cmdSet-Name2") + }) + + wFile.WriteString("cf api blah\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("bad command\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("stop\n") + time.Sleep(100 * time.Millisecond) + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(1)) + }) + + It("calls CLI to execute the cf command", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + go io_helpers.CaptureOutput(func() { + recorder.Record("cmdSet-Name2") + }) + + wFile.WriteString("cf api abc\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("cf target\n") + time.Sleep(100 * time.Millisecond) + wFile.WriteString("stop\n") + time.Sleep(100 * time.Millisecond) + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(2)) + Ω(fakeCliConnection.CliCommandArgsForCall(0)).To(Equal([]string{"api", "abc"})) + Ω(fakeCliConnection.CliCommandArgsForCall(1)).To(Equal([]string{"target"})) + }) + }) + + Describe("ListCmdSets()", func() { + It("prints all the available command sets", func() { + fakeData.ListCmdSetNamesReturns([]string{"cmdSet1", "cmdSet2", "cmdSet3"}) + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + + output := io_helpers.CaptureOutput(func() { + recorder.ListCmdSets() + }) + + Ω(output[0]).Should(ContainSubstring("following command sets are available")) + Ω(output[1]).Should(ContainSubstring("============")) + Ω(output[2]).Should(ContainSubstring("cmdSet1")) + Ω(output[3]).Should(ContainSubstring("cmdSet2")) + Ω(output[4]).Should(ContainSubstring("cmdSet3")) + }) + + Describe("ListCmds()", func() { + It("lists the commands within a command set", func() { + fakeData.GetCmdSetReturns([]string{"cmd1", "cmd2", "cmd3"}) + + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + + output := io_helpers.CaptureOutput(func() { + recorder.ListCmds("cmdSet1") + }) + + Ω(output[0]).Should(ContainSubstring("The following commands are in cmdSet1")) + Ω(output[1]).Should(ContainSubstring("==========")) + Ω(output[2]).Should(ContainSubstring("cmd1")) + Ω(output[3]).Should(ContainSubstring("cmd2")) + Ω(output[4]).Should(ContainSubstring("cmd3")) + }) + }) + + Describe("DeleteCmdSet()", func() { + It("calls the data store to delete a command set", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + recorder.DeleteCmdSet("delete-set") + + Ω(fakeData.DeleteCmdSetCallCount()).To(Equal(1)) + Ω(fakeData.DeleteCmdSetArgsForCall(0)).To(Equal("delete-set")) + }) + }) + + Describe("ClearCmdSet()", func() { + It("calls the data store to clear all recorded command set", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + + go io_helpers.CaptureOutput(func() { + recorder.ClearCmdSets() + }) + + wFile.WriteString("y\n") + time.Sleep(100 * time.Millisecond) + + Ω(fakeData.ClearCmdSetsCallCount()).To(Equal(1)) + }) + + It("does not clear command sets without user confirmation", func() { + recorder = record.NewRecordCmd(fakeCliConnection, fakeData, rFile) + + go io_helpers.CaptureOutput(func() { + recorder.ClearCmdSets() + }) + + wFile.WriteString("n\n") + time.Sleep(100 * time.Millisecond) + + Ω(fakeData.ClearCmdSetsCallCount()).To(Equal(0)) + }) + }) + }) + +}) diff --git a/replay/replay.go b/replay/replay.go index df36f95..49e0654 100644 --- a/replay/replay.go +++ b/replay/replay.go @@ -13,22 +13,22 @@ type ReplayCmds interface { } type replayCmds struct { - cli plugin.CliConnection - cmdsets []string + cli plugin.CliConnection + cmdsets []string + cmdSetData data.CmdSetData } -func NewReplayCmds(cli plugin.CliConnection, cmdsets ...string) ReplayCmds { +func NewReplayCmds(cli plugin.CliConnection, cmdSetData data.CmdSetData, cmdsets ...string) ReplayCmds { return &replayCmds{ - cli: cli, - cmdsets: cmdsets, + cli: cli, + cmdsets: cmdsets, + cmdSetData: cmdSetData, } } func (p *replayCmds) Run() { - c := data.NewCmdSetData() - for _, cmdset := range p.cmdsets { - cmds := c.GetCmdSet(cmdset) + cmds := p.cmdSetData.GetCmdSet(cmdset) if len(cmds) == 0 { fmt.Printf("Command set %s not found.\n\n", cmdset) diff --git a/replay/replay_suite_test.go b/replay/replay_suite_test.go new file mode 100644 index 0000000..6bd215b --- /dev/null +++ b/replay/replay_suite_test.go @@ -0,0 +1,13 @@ +package replay_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestReplay(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Replay Suite") +} diff --git a/replay/replay_test.go b/replay/replay_test.go new file mode 100644 index 0000000..254cdba --- /dev/null +++ b/replay/replay_test.go @@ -0,0 +1,86 @@ +package replay_test + +import ( + "github.com/cloudfoundry/cli/plugin/fakes" + io_helpers "github.com/cloudfoundry/cli/testhelpers/io" + + "github.com/simonleung8/cli-plugin-recorder/data/data_fakes" + "github.com/simonleung8/cli-plugin-recorder/replay" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Replay", func() { + var ( + fakeData *data_fakes.FakeCmdSetData + fakeCliConnection *fakes.FakeCliConnection + cmds replay.ReplayCmds + ) + + BeforeEach(func() { + fakeCliConnection = &fakes.FakeCliConnection{} + fakeData = &data_fakes.FakeCmdSetData{} + + fakeData.GetCmdSetStub = func(cmd string) []string { + if cmd == "set1" { + return []string{"cmd1 arg1", "cmd2"} + } else if cmd == "set2" { + return []string{"cmda arg1 arg2", "cmdb arg1"} + } + return []string{} + } + + }) + + Describe("Run()", func() { + It("retreive the command set and pass all commands to CLI for execution", func() { + cmds = replay.NewReplayCmds(fakeCliConnection, fakeData, "set1") + cmds.Run() + + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(2)) + + args1 := fakeCliConnection.CliCommandArgsForCall(0) + args2 := fakeCliConnection.CliCommandArgsForCall(1) + + Ω(args1).To(Equal([]string{"cmd1", "arg1"})) + Ω(args2).To(Equal([]string{"cmd2"})) + }) + + It("accept multiple command sets", func() { + cmds = replay.NewReplayCmds(fakeCliConnection, fakeData, "set1", "set2") + cmds.Run() + + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(4)) + + args1 := fakeCliConnection.CliCommandArgsForCall(0) + args2 := fakeCliConnection.CliCommandArgsForCall(1) + args3 := fakeCliConnection.CliCommandArgsForCall(2) + args4 := fakeCliConnection.CliCommandArgsForCall(3) + + Ω(args1).To(Equal([]string{"cmd1", "arg1"})) + Ω(args2).To(Equal([]string{"cmd2"})) + Ω(args3).To(Equal([]string{"cmda", "arg1", "arg2"})) + Ω(args4).To(Equal([]string{"cmdb", "arg1"})) + }) + + It("reports when a command set is not found", func() { + cmds = replay.NewReplayCmds(fakeCliConnection, fakeData, "bad-set", "set1") + output := io_helpers.CaptureOutput(func() { + cmds.Run() + }) + + Ω(output[0]).To(ContainSubstring("Command set bad-set not found")) + + Ω(fakeCliConnection.CliCommandCallCount()).To(Equal(2)) + + args1 := fakeCliConnection.CliCommandArgsForCall(0) + args2 := fakeCliConnection.CliCommandArgsForCall(1) + + Ω(args1).To(Equal([]string{"cmd1", "arg1"})) + Ω(args2).To(Equal([]string{"cmd2"})) + }) + + }) + +})