github.com/LukasHeimann/cloudfoundrycli/v8@v8.4.4/integration/v7/isolated/rollback_command_test.go (about) 1 package isolated 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "path/filepath" 7 8 . "github.com/LukasHeimann/cloudfoundrycli/v8/cf/util/testhelpers/matchers" 9 "github.com/LukasHeimann/cloudfoundrycli/v8/integration/helpers" 10 . "github.com/onsi/ginkgo" 11 . "github.com/onsi/gomega" 12 . "github.com/onsi/gomega/gbytes" 13 . "github.com/onsi/gomega/gexec" 14 "github.com/onsi/gomega/types" 15 ) 16 17 var _ = Describe("rollback command", func() { 18 19 Describe("help", func() { 20 When("--help flag is set", func() { 21 It("appears in cf help -a", func() { 22 session := helpers.CF("help", "-a") 23 Eventually(session).Should(Exit(0)) 24 Expect(session).To(HaveCommandInCategoryWithDescription("rollback", "EXPERIMENTAL COMMANDS", "Rollback to the specified revision of an app")) 25 }) 26 27 It("Displays rollback command usage to output", func() { 28 session := helpers.CF("rollback", "--help") 29 30 Eventually(session).Should(Exit(0)) 31 32 Expect(session).To(Say("NAME:")) 33 Expect(session).To(Say("rollback - Rollback to the specified revision of an app")) 34 Expect(session).To(Say("USAGE:")) 35 Expect(session).To(Say(`cf rollback APP_NAME \[--version VERSION\]`)) 36 Expect(session).To(Say("OPTIONS:")) 37 Expect(session).To(Say("-f Force rollback without confirmation")) 38 Expect(session).To(Say("--version Roll back to the specified revision")) 39 Expect(session).To(Say("SEE ALSO:")) 40 Expect(session).To(Say("revisions")) 41 }) 42 }) 43 }) 44 45 When("the environment is set up correctly", func() { 46 var ( 47 appName string 48 orgName string 49 spaceName string 50 userName string 51 ) 52 53 BeforeEach(func() { 54 appName = helpers.PrefixedRandomName("app") 55 orgName = helpers.NewOrgName() 56 spaceName = helpers.NewSpaceName() 57 userName, _ = helpers.GetCredentials() 58 helpers.SetupCF(orgName, spaceName) 59 }) 60 61 AfterEach(func() { 62 helpers.QuickDeleteOrg(orgName) 63 }) 64 65 Describe("the app does not exist", func() { 66 It("errors with app not found", func() { 67 session := helpers.CF("rollback", appName, "--version", "1") 68 Eventually(session).Should(Exit(1)) 69 70 Expect(session).ToNot(Say("Are you sure you want to continue?")) 71 72 Expect(session.Err).To(Say("App '%s' not found.", appName)) 73 Expect(session).To(Say("FAILED")) 74 }) 75 }) 76 77 Describe("the app exists with revisions", func() { 78 When("the app is started and has 2 instances", func() { 79 var domainName string 80 81 BeforeEach(func() { 82 domainName = helpers.DefaultSharedDomain() 83 helpers.WithHelloWorldApp(func(appDir string) { 84 manifestContents := []byte(fmt.Sprintf(` 85 --- 86 applications: 87 - name: %s 88 memory: 128M 89 instances: 2 90 disk_quota: 128M 91 routes: 92 - route: %s.%s 93 `, appName, appName, domainName)) 94 manifestPath := filepath.Join(appDir, "manifest.yml") 95 err := ioutil.WriteFile(manifestPath, manifestContents, 0666) 96 Expect(err).ToNot(HaveOccurred()) 97 98 Eventually(helpers.CF("push", appName, "-p", appDir, "-f", manifestPath, "-b", "staticfile_buildpack")).Should(Exit(0)) 99 Eventually(helpers.CF("push", appName, "-p", appDir, "-f", manifestPath, "-b", "staticfile_buildpack")).Should(Exit(0)) 100 }) 101 }) 102 103 When("the desired revision does not exist", func() { 104 It("errors with 'revision not found'", func() { 105 session := helpers.CF("rollback", appName, "--version", "5") 106 Eventually(session).Should(Exit(1)) 107 108 Expect(session.Err).To(Say("Revision '5' not found")) 109 Expect(session).To(Say("FAILED")) 110 }) 111 }) 112 113 When("the -f flag is provided", func() { 114 It("does not prompt the user, and just rolls back", func() { 115 session := helpers.CF("rollback", appName, "--version", "1", "-f") 116 Eventually(session).Should(Exit(0)) 117 118 Expect(session).To(HaveRollbackOutput(appName, orgName, spaceName, userName)) 119 120 session = helpers.CF("revisions", appName) 121 Eventually(session).Should(Exit(0)) 122 }) 123 }) 124 125 Describe("the -f flag is not provided", func() { 126 var buffer *Buffer 127 128 BeforeEach(func() { 129 buffer = NewBuffer() 130 }) 131 132 When("the user enters y", func() { 133 BeforeEach(func() { 134 _, err := buffer.Write([]byte("y\n")) 135 Expect(err).ToNot(HaveOccurred()) 136 }) 137 138 It("prompts the user to rollback, then successfully rolls back", func() { 139 session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1") 140 Eventually(session).Should(Exit(0)) 141 142 Expect(session).To(HaveRollbackPrompt()) 143 Expect(session).To(HaveRollbackOutput(appName, orgName, spaceName, userName)) 144 Expect(session).To(Say("OK")) 145 146 session = helpers.CF("revisions", appName) 147 Eventually(session).Should(Exit(0)) 148 149 Expect(session).To(Say(`3\(deployed\)\s+New droplet deployed.`)) 150 }) 151 }) 152 153 When("the user enters n", func() { 154 BeforeEach(func() { 155 _, err := buffer.Write([]byte("n\n")) 156 Expect(err).ToNot(HaveOccurred()) 157 }) 158 159 It("prompts the user to rollback, then does not rollback", func() { 160 session := helpers.CFWithStdin(buffer, "rollback", appName, "--version", "1") 161 Eventually(session).Should(Exit(0)) 162 163 Expect(session).To(HaveRollbackPrompt()) 164 Expect(session).To(Say("App '%s' has not been rolled back to revision '1'", appName)) 165 166 session = helpers.CF("revisions", appName) 167 Eventually(session).Should(Exit(0)) 168 169 Expect(session).ToNot(Say(`3\s+[\w\-]+\s+New droplet deployed.`)) 170 }) 171 }) 172 }) 173 }) 174 }) 175 }) 176 }) 177 178 type Line struct { 179 str string 180 args []interface{} 181 } 182 183 func HaveRollbackPrompt() *CLIMatcher { 184 return &CLIMatcher{Lines: []Line{ 185 {"Are you sure you want to continue?", nil}, 186 }} 187 } 188 189 func HaveRollbackOutput(appName, orgName, spaceName, userName string) *CLIMatcher { 190 return &CLIMatcher{Lines: []Line{ 191 AppInOrgSpaceAsUser(appName, orgName, spaceName, userName), 192 KeyValue("name", appName), 193 KeyValue("routes", fmt.Sprintf("%s.%s", appName, helpers.DefaultSharedDomain())), 194 }} 195 } 196 197 // Per-style guide: https://github.com/cloudfoundry/cli/wiki/CF-CLI-Style-Guide#system-feedback--transparency 198 func AppInOrgSpaceAsUser(appName, orgName, spaceName, userName string) Line { 199 return Line{`app %s in org %s / space %s as %s`, []interface{}{appName, orgName, spaceName, userName}} 200 } 201 202 // Per-style guide: https://github.com/cloudfoundry/cli/wiki/CF-CLI-Style-Guide#keyvalue-pairs 203 func KeyValue(key, value string) Line { 204 return Line{`%s:\s+%s`, []interface{}{key, value}} 205 } 206 207 type CLIMatcher struct { 208 Lines []Line 209 wrappedMatcher types.GomegaMatcher 210 } 211 212 func (cm *CLIMatcher) Match(actual interface{}) (bool, error) { 213 for _, line := range cm.Lines { 214 cm.wrappedMatcher = types.GomegaMatcher(Say(line.str, line.args...)) 215 success, err := cm.wrappedMatcher.Match(actual) 216 if err != nil { 217 return false, err 218 } 219 220 if success != true { 221 return false, nil 222 } 223 } 224 225 return true, nil 226 } 227 228 func (cm CLIMatcher) FailureMessage(interface{}) string { 229 return cm.wrappedMatcher.FailureMessage(nil) 230 } 231 232 func (cm CLIMatcher) NegatedFailureMessage(interface{}) string { 233 return cm.wrappedMatcher.NegatedFailureMessage(nil) 234 }