github.com/ouraigua/jenkins-library@v0.0.0-20231028010029-fbeaf2f3aa9b/cmd/xsDeploy_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package cmd
     5  
     6  import (
     7  	"bytes"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"sync"
    12  	"testing"
    13  
    14  	"github.com/SAP/jenkins-library/pkg/log"
    15  	"github.com/SAP/jenkins-library/pkg/mock"
    16  	"github.com/stretchr/testify/assert"
    17  )
    18  
    19  type FileUtilsMock struct {
    20  	*mock.FilesMock
    21  	copiedFiles []string
    22  }
    23  
    24  func (f *FileUtilsMock) FileExists(path string) (bool, error) {
    25  	return path == "dummy.mtar" || path == ".xs_session", nil
    26  }
    27  
    28  func (f *FileUtilsMock) Copy(src, dest string) (int64, error) {
    29  	f.copiedFiles = append(f.copiedFiles, fmt.Sprintf("%s->%s", src, dest))
    30  	return 0, nil
    31  }
    32  
    33  func TestDeploy(t *testing.T) {
    34  	myXsDeployOptions := xsDeployOptions{
    35  		APIURL:                "https://example.org:12345",
    36  		Username:              "me",
    37  		Password:              "secretPassword",
    38  		Org:                   "myOrg",
    39  		Space:                 "mySpace",
    40  		LoginOpts:             "--skip-ssl-validation",
    41  		DeployOpts:            "--dummy-deploy-opts",
    42  		XsSessionFile:         ".xs_session",
    43  		Mode:                  "DEPLOY",
    44  		Action:                "NONE",
    45  		MtaPath:               "dummy.mtar",
    46  		OperationIDLogPattern: `^.*xs bg-deploy -i (.*) -a.*$`,
    47  	}
    48  
    49  	s := mock.ShellMockRunner{}
    50  
    51  	var removedFiles []string
    52  
    53  	cpeOut := xsDeployCommonPipelineEnvironment{}
    54  	fileUtilsMock := FileUtilsMock{}
    55  
    56  	fRemove := func(path string) error {
    57  		removedFiles = append(removedFiles, path)
    58  		return nil
    59  	}
    60  
    61  	var stdout string
    62  
    63  	t.Run("Standard deploy succeeds", func(t *testing.T) {
    64  
    65  		defer func() {
    66  			fileUtilsMock.copiedFiles = nil
    67  			removedFiles = nil
    68  			s.Calls = nil
    69  			stdout = ""
    70  		}()
    71  
    72  		rStdout, wStdout := io.Pipe()
    73  
    74  		var wg sync.WaitGroup
    75  		wg.Add(1)
    76  
    77  		go func() {
    78  			buf := new(bytes.Buffer)
    79  			if _, err := io.Copy(buf, rStdout); err != nil {
    80  				log.Entry().Warning("failed to copy buffer")
    81  			}
    82  			stdout = buf.String()
    83  			wg.Done()
    84  		}()
    85  
    86  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, wStdout)
    87  
    88  		wStdout.Close()
    89  		wg.Wait()
    90  
    91  		assert.NoError(t, e)
    92  
    93  		t.Run("Standard checks", func(t *testing.T) {
    94  			// Contains --> we do not check for the shebang
    95  			assert.Contains(t, s.Calls[0], "xs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation")
    96  			assert.Contains(t, s.Calls[1], "xs deploy dummy.mtar --dummy-deploy-opts")
    97  			assert.Contains(t, s.Calls[2], "xs logout")
    98  			assert.Len(t, s.Calls, 3)
    99  
   100  			// xs session file needs to be removed at end during a normal deployment
   101  			assert.Len(t, removedFiles, 1)
   102  			assert.Contains(t, removedFiles, ".xs_session")
   103  
   104  			assert.Len(t, fileUtilsMock.copiedFiles, 2)
   105  			// We copy the xs session file to the workspace in order to be able to use the file later.
   106  			// This happens directly after login
   107  			// We copy the xs session file from the workspace to the home folder in order to be able to
   108  			// use that file. This is important in case we rely on a login which happened e
   109  			assert.Contains(t, fileUtilsMock.copiedFiles[0], "/.xs_session->.xs_session")
   110  			assert.Contains(t, fileUtilsMock.copiedFiles[1], ".xs_session->")
   111  			assert.Contains(t, fileUtilsMock.copiedFiles[1], "/.xs_session")
   112  		})
   113  
   114  		t.Run("Password not exposed", func(t *testing.T) {
   115  			assert.NotEmpty(t, stdout)
   116  			assert.NotContains(t, stdout, myXsDeployOptions.Password)
   117  		})
   118  	})
   119  
   120  	t.Run("Standard deploy fails, deployable missing", func(t *testing.T) {
   121  
   122  		defer func() {
   123  			fileUtilsMock.copiedFiles = nil
   124  			removedFiles = nil
   125  			s.Calls = nil
   126  		}()
   127  
   128  		oldMtaPath := myXsDeployOptions.MtaPath
   129  
   130  		defer func() {
   131  			myXsDeployOptions.MtaPath = oldMtaPath
   132  		}()
   133  
   134  		// this file is not denoted in the file exists mock
   135  		myXsDeployOptions.MtaPath = "doesNotExist"
   136  
   137  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   138  		assert.EqualError(t, e, "Deployable 'doesNotExist' does not exist")
   139  	})
   140  
   141  	t.Run("Standard deploy fails, action provided", func(t *testing.T) {
   142  
   143  		defer func() {
   144  			fileUtilsMock.copiedFiles = nil
   145  			removedFiles = nil
   146  			s.Calls = nil
   147  		}()
   148  
   149  		myXsDeployOptions.Action = "RETRY"
   150  		defer func() {
   151  			myXsDeployOptions.Action = "NONE"
   152  		}()
   153  
   154  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   155  		assert.EqualError(t, e, "Cannot perform action 'RETRY' in mode 'DEPLOY'. Only action 'NONE' is allowed.")
   156  	})
   157  
   158  	t.Run("Standard deploy fails, error from underlying process", func(t *testing.T) {
   159  
   160  		defer func() {
   161  			fileUtilsMock.copiedFiles = nil
   162  			removedFiles = nil
   163  			s.Calls = nil
   164  			s.ShouldFailOnCommand = nil
   165  		}()
   166  
   167  		s.ShouldFailOnCommand = map[string]error{"#!/bin/bash\nxs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation\n": errors.New("Error from underlying process")}
   168  
   169  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   170  		assert.EqualError(t, e, "Error from underlying process")
   171  	})
   172  
   173  	t.Run("BG deploy succeeds", func(t *testing.T) {
   174  
   175  		defer func() {
   176  			fileUtilsMock.copiedFiles = nil
   177  			removedFiles = nil
   178  			s.Calls = nil
   179  			s.StdoutReturn = make(map[string]string)
   180  		}()
   181  
   182  		s.StdoutReturn = make(map[string]string)
   183  		s.StdoutReturn[".*xs bg-deploy.*"] = "Use \"xs bg-deploy -i 1234 -a resume\" to resume the process.\n"
   184  
   185  		oldMode := myXsDeployOptions.Mode
   186  
   187  		defer func() {
   188  			myXsDeployOptions.Mode = oldMode
   189  		}()
   190  
   191  		myXsDeployOptions.Mode = "BG_DEPLOY"
   192  
   193  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   194  
   195  		if assert.NoError(t, e) {
   196  			if assert.Len(t, s.Calls, 2) { // There are two entries --> no logout in this case.
   197  				assert.Contains(t, s.Calls[0], "xs login")
   198  				assert.Contains(t, s.Calls[1], "xs bg-deploy dummy.mtar --dummy-deploy-opts")
   199  			}
   200  		}
   201  	})
   202  
   203  	t.Run("BG deploy fails, missing operationID", func(t *testing.T) {
   204  
   205  		s.StdoutReturn = make(map[string]string)
   206  		s.StdoutReturn[".*bg_deploy.*"] = "There is no operationID ...\n"
   207  		defer func() {
   208  			fileUtilsMock.copiedFiles = nil
   209  			removedFiles = nil
   210  			s.Calls = nil
   211  			s.StdoutReturn = make(map[string]string)
   212  		}()
   213  
   214  		oldMode := myXsDeployOptions.Mode
   215  
   216  		defer func() {
   217  			myXsDeployOptions.Mode = oldMode
   218  		}()
   219  
   220  		myXsDeployOptions.Mode = "BG_DEPLOY"
   221  
   222  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   223  		assert.EqualError(t, e, "No operationID found")
   224  	})
   225  
   226  	t.Run("BG deploy abort succeeds", func(t *testing.T) {
   227  
   228  		defer func() {
   229  			fileUtilsMock.copiedFiles = nil
   230  			removedFiles = nil
   231  			s.Calls = nil
   232  		}()
   233  
   234  		oldMode := myXsDeployOptions.Mode
   235  		oldAction := myXsDeployOptions.Action
   236  
   237  		defer func() {
   238  			myXsDeployOptions.Mode = oldMode
   239  			myXsDeployOptions.Action = oldAction
   240  			myXsDeployOptions.OperationID = ""
   241  		}()
   242  
   243  		myXsDeployOptions.Mode = "BG_DEPLOY"
   244  		myXsDeployOptions.Action = "ABORT"
   245  		myXsDeployOptions.OperationID = "12345"
   246  
   247  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   248  
   249  		if assert.NoError(t, e) {
   250  			if assert.Len(t, s.Calls, 2) { // There is no login --> we have two calls
   251  				assert.Contains(t, s.Calls[0], "xs bg-deploy -i 12345 -a abort")
   252  				assert.Contains(t, s.Calls[1], "xs logout")
   253  			}
   254  
   255  		}
   256  	})
   257  
   258  	t.Run("BG deploy abort fails due to missing operationId", func(t *testing.T) {
   259  
   260  		defer func() {
   261  			fileUtilsMock.copiedFiles = nil
   262  			removedFiles = nil
   263  			s.Calls = nil
   264  		}()
   265  
   266  		oldMode := myXsDeployOptions.Mode
   267  		oldAction := myXsDeployOptions.Action
   268  
   269  		defer func() {
   270  			myXsDeployOptions.Mode = oldMode
   271  			myXsDeployOptions.Action = oldAction
   272  		}()
   273  
   274  		myXsDeployOptions.Mode = "BG_DEPLOY"
   275  		myXsDeployOptions.Action = "ABORT"
   276  
   277  		e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, io.Discard)
   278  		assert.EqualError(t, e, "OperationID was not provided. This is required for action 'ABORT'.")
   279  	})
   280  }
   281  
   282  func TestRetrieveOperationID(t *testing.T) {
   283  	operationID := retrieveOperationID(`
   284  	Uploading 1 files:
   285          myFolder/dummy.mtar
   286  	File upload finished
   287  
   288  	Detected MTA schema version: "3.1.0"
   289  	Detected deploy target as "myOrg mySpace"
   290  	Detected deployed MTA with ID "my_mta" and version "0.0.1"
   291  	Deployed MTA color: blue
   292  	New MTA color: green
   293  	Detected new MTA version: "0.0.1"
   294  	Deployed MTA version: 0.0.1
   295  	Service "xxx" is not modified and will not be updated
   296  	Creating application "db-green" from MTA module "xx"...
   297  	Uploading application "xx-green"...
   298  	Staging application "xx-green"...
   299  	Application "xx-green" staged
   300  	Executing task "deploy" on application "xx-green"...
   301  	Task execution status: succeeded
   302  	Process has entered validation phase. After testing your new deployment you can resume or abort the process.
   303  	Use "xs bg-deploy -i 1234 -a resume" to resume the process.
   304  	Use "xs bg-deploy -i 1234 -a abort" to abort the process.
   305  	Hint: Use the '--no-confirm' option of the bg-deploy command to skip this phase.
   306  	`, `^.*xs bg-deploy -i (.*) -a.*$`)
   307  
   308  	assert.Equal(t, "1234", operationID)
   309  }