1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00
sap-jenkins-library/cmd/xsDeploy_test.go

328 lines
9.4 KiB
Go
Raw Normal View History

package cmd
import (
"bytes"
"errors"
"fmt"
"github.com/SAP/jenkins-library/pkg/mock"
"github.com/stretchr/testify/assert"
"io"
"io/ioutil"
"os"
"sync"
"testing"
)
type FileUtilsMock struct {
copiedFiles []string
}
func (f *FileUtilsMock) FileExists(path string) (bool, error) {
return path == "dummy.mtar" || path == ".xs_session", nil
}
func (f *FileUtilsMock) Copy(src, dest string) (int64, error) {
f.copiedFiles = append(f.copiedFiles, fmt.Sprintf("%s->%s", src, dest))
return 0, nil
}
func (f *FileUtilsMock) FileRead(path string) ([]byte, error) {
return []byte{}, nil
}
func (f *FileUtilsMock) FileWrite(path string, content []byte, perm os.FileMode) error {
return nil
}
func (f *FileUtilsMock) MkdirAll(path string, perm os.FileMode) error {
return nil
}
2020-07-16 14:25:01 +02:00
func (f *FileUtilsMock) Chmod(path string, mode os.FileMode) error {
return fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func (f *FileUtilsMock) Abs(path string) (string, error) {
return "", fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func (f *FileUtilsMock) Glob(pattern string) (matches []string, err error) {
return nil, fmt.Errorf("not implemented. func is only present in order to fullfil the interface contract. Needs to be ajusted in case it gets used.")
}
func TestDeploy(t *testing.T) {
myXsDeployOptions := xsDeployOptions{
APIURL: "https://example.org:12345",
User: "me",
Password: "secretPassword",
Org: "myOrg",
Space: "mySpace",
LoginOpts: "--skip-ssl-validation",
DeployOpts: "--dummy-deploy-opts",
XsSessionFile: ".xs_session",
Mode: "DEPLOY",
Action: "NONE",
MtaPath: "dummy.mtar",
OperationIDLogPattern: `^.*xs bg-deploy -i (.*) -a.*$`,
}
s := mock.ShellMockRunner{}
var removedFiles []string
cpeOut := xsDeployCommonPipelineEnvironment{}
fileUtilsMock := FileUtilsMock{}
fRemove := func(path string) error {
removedFiles = append(removedFiles, path)
return nil
}
var stdout string
t.Run("Standard deploy succeeds", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
stdout = ""
}()
rStdout, wStdout := io.Pipe()
var wg sync.WaitGroup
wg.Add(1)
go func() {
buf := new(bytes.Buffer)
io.Copy(buf, rStdout)
stdout = buf.String()
wg.Done()
}()
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, wStdout)
wStdout.Close()
wg.Wait()
assert.NoError(t, e)
t.Run("Standard checks", func(t *testing.T) {
// Contains --> we do not check for the shebang
assert.Contains(t, s.Calls[0], "xs login -a https://example.org:12345 -u me -p 'secretPassword' -o myOrg -s mySpace --skip-ssl-validation")
assert.Contains(t, s.Calls[1], "xs deploy dummy.mtar --dummy-deploy-opts")
assert.Contains(t, s.Calls[2], "xs logout")
assert.Len(t, s.Calls, 3)
// xs session file needs to be removed at end during a normal deployment
assert.Len(t, removedFiles, 1)
assert.Contains(t, removedFiles, ".xs_session")
assert.Len(t, fileUtilsMock.copiedFiles, 2)
// We copy the xs session file to the workspace in order to be able to use the file later.
// This happens directly after login
// We copy the xs session file from the workspace to the home folder in order to be able to
// use that file. This is important in case we rely on a login which happend e
assert.Contains(t, fileUtilsMock.copiedFiles[0], "/.xs_session->.xs_session")
assert.Contains(t, fileUtilsMock.copiedFiles[1], ".xs_session->")
assert.Contains(t, fileUtilsMock.copiedFiles[1], "/.xs_session")
})
t.Run("Password not exposed", func(t *testing.T) {
assert.NotEmpty(t, stdout)
assert.NotContains(t, stdout, myXsDeployOptions.Password)
})
})
t.Run("Standard deploy fails, deployable missing", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
}()
oldMtaPath := myXsDeployOptions.MtaPath
defer func() {
myXsDeployOptions.MtaPath = oldMtaPath
}()
// this file is not denoted in the file exists mock
myXsDeployOptions.MtaPath = "doesNotExist"
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
assert.EqualError(t, e, "Deployable 'doesNotExist' does not exist")
})
t.Run("Standard deploy fails, action provided", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
}()
myXsDeployOptions.Action = "RETRY"
defer func() {
myXsDeployOptions.Action = "NONE"
}()
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
assert.EqualError(t, e, "Cannot perform action 'RETRY' in mode 'DEPLOY'. Only action 'NONE' is allowed.")
})
t.Run("Standard deploy fails, error from underlying process", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
s.ShouldFailOnCommand = nil
}()
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")}
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
assert.EqualError(t, e, "Error from underlying process")
})
t.Run("BG deploy succeeds", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
s.StdoutReturn = make(map[string]string)
}()
s.StdoutReturn = make(map[string]string)
s.StdoutReturn[".*xs bg-deploy.*"] = "Use \"xs bg-deploy -i 1234 -a resume\" to resume the process.\n"
oldMode := myXsDeployOptions.Mode
defer func() {
myXsDeployOptions.Mode = oldMode
}()
myXsDeployOptions.Mode = "BG_DEPLOY"
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
if assert.NoError(t, e) {
if assert.Len(t, s.Calls, 2) { // There are two entries --> no logout in this case.
assert.Contains(t, s.Calls[0], "xs login")
assert.Contains(t, s.Calls[1], "xs bg-deploy dummy.mtar --dummy-deploy-opts")
}
}
})
t.Run("BG deploy fails, missing operationID", func(t *testing.T) {
s.StdoutReturn = make(map[string]string)
s.StdoutReturn[".*bg_deploy.*"] = "There is no operationID ...\n"
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
s.StdoutReturn = make(map[string]string)
}()
oldMode := myXsDeployOptions.Mode
defer func() {
myXsDeployOptions.Mode = oldMode
}()
myXsDeployOptions.Mode = "BG_DEPLOY"
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
assert.EqualError(t, e, "No operationID found")
})
t.Run("BG deploy abort succeeds", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
}()
oldMode := myXsDeployOptions.Mode
oldAction := myXsDeployOptions.Action
defer func() {
myXsDeployOptions.Mode = oldMode
myXsDeployOptions.Action = oldAction
myXsDeployOptions.OperationID = ""
}()
myXsDeployOptions.Mode = "BG_DEPLOY"
myXsDeployOptions.Action = "ABORT"
myXsDeployOptions.OperationID = "12345"
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
if assert.NoError(t, e) {
if assert.Len(t, s.Calls, 2) { // There is no login --> we have two calls
assert.Contains(t, s.Calls[0], "xs bg-deploy -i 12345 -a abort")
assert.Contains(t, s.Calls[1], "xs logout")
}
}
})
t.Run("BG deploy abort fails due to missing operationId", func(t *testing.T) {
defer func() {
fileUtilsMock.copiedFiles = nil
removedFiles = nil
s.Calls = nil
}()
oldMode := myXsDeployOptions.Mode
oldAction := myXsDeployOptions.Action
defer func() {
myXsDeployOptions.Mode = oldMode
myXsDeployOptions.Action = oldAction
}()
myXsDeployOptions.Mode = "BG_DEPLOY"
myXsDeployOptions.Action = "ABORT"
e := runXsDeploy(myXsDeployOptions, &cpeOut, &s, &fileUtilsMock, fRemove, ioutil.Discard)
assert.EqualError(t, e, "OperationID was not provided. This is required for action 'ABORT'.")
})
}
func TestRetrieveOperationID(t *testing.T) {
operationID := retrieveOperationID(`
Uploading 1 files:
myFolder/dummy.mtar
File upload finished
Detected MTA schema version: "3.1.0"
Detected deploy target as "myOrg mySpace"
Detected deployed MTA with ID "my_mta" and version "0.0.1"
Deployed MTA color: blue
New MTA color: green
Detected new MTA version: "0.0.1"
Deployed MTA version: 0.0.1
Service "xxx" is not modified and will not be updated
Creating application "db-green" from MTA module "xx"...
Uploading application "xx-green"...
Staging application "xx-green"...
Application "xx-green" staged
Executing task "deploy" on application "xx-green"...
Task execution status: succeeded
Process has entered validation phase. After testing your new deployment you can resume or abort the process.
Use "xs bg-deploy -i 1234 -a resume" to resume the process.
Use "xs bg-deploy -i 1234 -a abort" to abort the process.
Hint: Use the '--no-confirm' option of the bg-deploy command to skip this phase.
`, `^.*xs bg-deploy -i (.*) -a.*$`)
assert.Equal(t, "1234", operationID)
}