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  }