import com.sap.piper.StepAssertions import com.sap.piper.Utils import hudson.AbortException import util.JenkinsReadFileRule import util.JenkinsReadJsonRule import static org.hamcrest.Matchers.allOf import static org.hamcrest.Matchers.containsString import static org.hamcrest.Matchers.hasItem import static org.hamcrest.Matchers.not import static org.junit.Assert.assertThat import org.hamcrest.BaseMatcher import org.hamcrest.Description import org.jenkinsci.plugins.credentialsbinding.impl.CredentialNotFoundException import org.junit.After import org.junit.Assert import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.rules.ExpectedException import org.junit.rules.RuleChain import util.BasePiperTest import util.CommandLineMatcher import util.JenkinsCredentialsRule import util.JenkinsLockRule import util.JenkinsLoggingRule import util.JenkinsPropertiesRule import util.JenkinsReadYamlRule import util.JenkinsShellCallRule import util.JenkinsShellCallRule.Type import util.JenkinsStepRule import util.JenkinsWithEnvRule import util.JenkinsFileExistsRule import util.Rules class NeoDeployTest extends BasePiperTest { private ExpectedException thrown = new ExpectedException().none() private JenkinsLoggingRule loggingRule = new JenkinsLoggingRule(this) private JenkinsShellCallRule shellRule = new JenkinsShellCallRule(this) private JenkinsStepRule stepRule = new JenkinsStepRule(this) private JenkinsReadFileRule readFileRule = new JenkinsReadFileRule(this, 'test/resources') private JenkinsFileExistsRule fileExistsRule = new JenkinsFileExistsRule(this, ['warArchive.war', 'archive.mtar', 'war.properties']) @Rule public RuleChain ruleChain = Rules .getCommonRules(this) .around(new JenkinsReadYamlRule(this)) .around(new JenkinsPropertiesRule(this, warPropertiesFileName, warProperties)) .around(thrown) .around(loggingRule) .around(shellRule) .around(new JenkinsCredentialsRule(this) .withCredentials('myCredentialsId', 'anonymous', '********') .withCredentials('CI_CREDENTIALS_ID', 'defaultUser', '********') .withCredentials('testOauthId', 'clientId', '********') .withCredentials("OauthDataFileId","oauth.json")) .around(new JenkinsReadJsonRule(this)) .around(readFileRule) .around(stepRule) .around(new JenkinsLockRule(this)) .around(new JenkinsWithEnvRule(this)) .around(fileExistsRule) private static warArchiveName = 'warArchive.war' private static warPropertiesFileName = 'war.properties' private static archiveName = 'archive.mtar' private static warProperties @Before void init() { warProperties = new Properties() warProperties.put('account', 'trialuser123') warProperties.put('host', 'test.deploy.host.com') warProperties.put('application', 'testApp') helper.registerAllowedMethod('dockerExecute', [Map, Closure], null) helper.registerAllowedMethod('pwd', [], { return './' }) nullScript.commonPipelineEnvironment.configuration = [steps: [neoDeploy: [neo: [host: 'test.deploy.host.com', account: 'trialuser123']]]] Utils.metaClass.echo = { def m -> } } @After void tearDown() { GroovySystem.metaClassRegistry.removeMetaClass(StepAssertions) GroovySystem.metaClassRegistry.removeMetaClass(Utils) } @Test void straightForwardTestConfigViaParameters() { stepRule.step.neoDeploy(script: nullScript, source: archiveName, neo:[credentialsId: 'myCredentialsId'], utils: utils, ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'trialuser123') .hasOption('synchronous', '') .hasSingleQuotedOption('user', 'anonymous') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*')) } @Test void straightForwardTestConfigViaConfiguration() { nullScript.commonPipelineEnvironment.configuration = [steps: [ neoDeploy: [ neo: [ host: 'configuration-frwk.deploy.host.com', account: 'configurationFrwkUser123' ], source: archiveName ] ]] stepRule.step.neoDeploy(script: nullScript, neo:[credentialsId: 'myCredentialsId'] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('host', 'configuration-frwk\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'configurationFrwkUser123') .hasOption('synchronous', '') .hasSingleQuotedOption('user', 'anonymous') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', archiveName)) } @Test void extensionsAsStringTest() { def checkedExtensionFiles = [] StepAssertions.metaClass.static.assertFileExists = getFileExistsCheck(checkedExtensionFiles, [archiveName, 'myExtension.yml']) stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: 'myExtension.yml' ) assert checkedExtensionFiles.contains('myExtension.yml') assertThat(shellRule.shell, new CommandLineMatcher() .hasProlog('neo.sh deploy-mta') .hasSingleQuotedOption('extensions', 'myExtension.yml')) } @Test void extensionsAsEmptyString() { thrown.expect(AbortException) thrown.expectMessage('extension file name was null or empty') stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: '' ) } @Test void extensionsAsSetTest() { Set extensions= ['myExtension1.yml' ,'myExtension2.yml'] extensionsAsCollectionTest(extensions) } @Test void extensionsAsCollectionWithEmptyStringTest() { thrown.expect(AbortException) thrown.expectMessage('extension file name was null or empty') stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: ['myExtension1.yml' ,'']) } @Test void extensionsNullTest() { stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: null) assert shellRule.shell.find { c -> c.startsWith('neo.sh deploy-mta') && ! c.contains('--extensions') } } @Test void extensionsAsEmptyCollectionTest() { stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: []) assert shellRule.shell.find { c -> c.startsWith('neo.sh deploy-mta') && ! c.contains('--extensions') } } @Test void extensionsAsCollectionsWithNullEntrySetTest() { thrown.expect(AbortException) thrown.expectMessage('extension file name was null or empty') stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: [null]) } @Test void extensionsAsListTest() { List extensions= ['myExtension1.yml' ,'myExtension2.yml'] extensionsAsCollectionTest(extensions) } @Test void sameExtensionProvidedTwiceTest() { List extensions= ['myExtension1.yml' ,'myExtension2.yml', 'myExtension1.yml'] extensionsAsCollectionTest(extensions) } void extensionsAsCollectionTest(def extensions) { def checkedExtensionFiles = [] StepAssertions.metaClass.static.assertFileExists = getFileExistsCheck(checkedExtensionFiles, [archiveName, 'myExtension1.yml', 'myExtension2.yml']) stepRule.step.neoDeploy( script: nullScript, source: archiveName, extensions: extensions ) assert checkedExtensionFiles.contains('myExtension1.yml') assert checkedExtensionFiles.contains('myExtension2.yml') assertThat(shellRule.shell, new CommandLineMatcher() .hasProlog('neo.sh deploy-mta') // some kind of creative usage for the single quotation check (... single quotes inside) .hasSingleQuotedOption('extensions', 'myExtension1.yml\',\'myExtension2.yml')) } private static getFileExistsCheck(def checkedExtensionFiles, def fileNames) { { Script step, String filePath -> checkedExtensionFiles << filePath if( ! fileNames.contains(filePath) ) step.error("File ${filePath} cannot be found.") } } @Test void extensionsForWrongDeployModeTest() { thrown.expect(AbortException) thrown.expectMessage('Extensions are only supported for deploy mode \'MTA\'') stepRule.step.neoDeploy( script: nullScript, source: archiveName, deployMode: 'warParams', extensions: 'myExtension.yml', neo: [ application: 'does', runtime: 'not', runtimeVersion: 'matter' ] ) } @Test void deployWithBearerTokenCredentials_success(){ nullScript.commonPipelineEnvironment.setMtarFilePath('archive.mtar') shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "https:\\/\\/api\\.test\\.com\\/oauth2\\/apitoken\\/v1", "{\"access_token\":\"xxx\"}") shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "https:\\/\\/slservice\\.test\\.host\\.com\\/slservice\\/v1\\/oauth\\/accounts\\/testUser123\\/mtars", "{\"id\":123}") shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "https:\\/\\/slservice\\.test\\.host\\.com\\/slservice\\/v1\\/oauth\\/accounts\\/testUser123\\/mtars", "{\"state\":\"DONE\"}") stepRule.step.neoDeploy( script: nullScript, source: archiveName, deployMode: 'mta', neo: [ host: 'test.host.com', account: 'testUser123', credentialsId: 'OauthDataFileId', credentialType: 'SecretFile' ], ) Assert.assertThat(shellRule.shell[0], containsString("#!/bin/bash curl --fail --silent --show-error --retry 12 -XPOST -u \"abc123:testclientsecret123\" \"https://api.test.com/oauth2/apitoken/v1?grant_type=client_credentials\"")) Assert.assertThat(shellRule.shell[1], containsString("#!/bin/bash curl --fail --silent --show-error --retry 12 -XPOST -H \"Authorization: Bearer xxx\" -F file=@\"archive.mtar\" \"https://slservice.test.host.com/slservice/v1/oauth/accounts/testUser123/mtars\"")) } @Test void archivePathFromCPETest() { nullScript.commonPipelineEnvironment.setMtarFilePath('archive.mtar') stepRule.step.neoDeploy(script: nullScript) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('source', 'archive.mtar')) } @Test void archivePathFromParamsHasHigherPrecedenceThanCPETest() { nullScript.commonPipelineEnvironment.setMtarFilePath('archive2.mtar') stepRule.step.neoDeploy(script: nullScript, source: "archive.mtar") Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('source', 'archive.mtar')) } @Test void badCredentialsIdTest() { thrown.expect(CredentialNotFoundException) stepRule.step.neoDeploy(script: nullScript, source: archiveName, neo:[credentialsId: 'badCredentialsId'] ) } @Test void credentialsIdNotProvidedTest() { stepRule.step.neoDeploy(script: nullScript, source: archiveName ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'trialuser123') .hasOption('synchronous', '') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*') ) } @Test void wrongArchivePathProvidedTest() { thrown.expect(AbortException) thrown.expectMessage('File wrongArchiveName cannot be found') stepRule.step.neoDeploy(script: nullScript, source: 'wrongArchiveName') } @Test void sanityChecksDeployModeMTATest() { thrown.expect(Exception) thrown.expectMessage( allOf( containsString('ERROR - NO VALUE AVAILABLE FOR:'), containsString('neo/host'), containsString('neo/account'), containsString('source'))) nullScript.commonPipelineEnvironment.configuration = [:] // deployMode mta is the default, but for the sake of transparency it is better to repeat it. stepRule.step.neoDeploy(script: nullScript, deployMode: 'mta') } @Test public void sanityChecksDeployModeWarPropertiesFileTest() { thrown.expect(IllegalArgumentException) // using this deploy mode 'account' and 'host' are provided by the properties file thrown.expectMessage( allOf( not(containsString('neo/host')), not(containsString('neo/account')))) nullScript.commonPipelineEnvironment.configuration = [:] stepRule.step.neoDeploy(script: nullScript, deployMode: 'warPropertiesFile', source: warArchiveName) } @Test public void sanityChecksDeployModeWarParamsTest() { thrown.expect(IllegalArgumentException) thrown.expectMessage( allOf( containsString('ERROR - NO VALUE AVAILABLE FOR:'), containsString('neo/application'), containsString('neo/runtime'), containsString('neo/runtimeVersion'), containsString('neo/host'), containsString('neo/account'))) nullScript.commonPipelineEnvironment.configuration = [:] stepRule.step.neoDeploy(script: nullScript, deployMode: 'warParams', source: warArchiveName) } @Test void mtaDeployModeTest() { stepRule.step.neoDeploy(script: nullScript, source: archiveName, deployMode: 'mta') Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy-mta") .hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'trialuser123') .hasOption('synchronous', '') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*')) } @Test void invalidateCache_Success_Test() { nullScript.commonPipelineEnvironment.configuration = [steps: [neoDeploy: [neo: [host: 'test.deploy.host.com', account: 'trialuser123', invalidateCache: true, oauthCredentialId: 'testOauthId', siteId: "12346"]]]] fileExistsRule.registerExistingFile('./target/artifact.war') shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "https:\\/\\/oauthasservices-trialuser123\\.test\\.deploy\\.host\\.com\\/oauth2\\/api\\/v1\\/token", "{\"access_token\":\"xxx\"}") shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "fiori\\/api\\/v1\\/csrf", "X-CSRF-Token=xxx") shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, "fiori\\/v1\\/operations\\/invalidateCache", "{\"statusCode\":\"200\"}") stepRule.step.neoDeploy(script: nullScript, source: archiveName, deployMode: 'mta') Assert.assertThat(shellRule.shell, hasItem(containsString("curl -X POST -u \"clientId:********\" --fail \"https://oauthasservices-trialuser123.test.deploy.host.com/oauth2/api/v1/token?grant_type=client_credentials&scope=write,read"))) Assert.assertThat(shellRule.shell[3], containsString("#!/bin/bash curl -i -L -c 'cookies.jar' -H 'X-CSRF-Token: Fetch' -H \"Authorization: Bearer xxx\" --fail \"https://cloudnwcportal-trialuser123.test.deploy.host.com/fiori/api/v1/csrf\"")) } @Test void invalidateCache_notPerformed_warFiles() { nullScript.commonPipelineEnvironment.configuration = [steps: [neoDeploy: [neo: [host: 'test.deploy.host.com', account: 'trialuser123', invalidateCache: true]]]] stepRule.step.neoDeploy(script: nullScript, neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', size: 'lite', ], deployMode: 'warParams', warAction: 'deploy', source: warArchiveName) assertThat(loggingRule.log, containsString("Invalidation of cache is ignored. It is performed only for html5 applications.")) } @Test void warFileParamsDeployModeTest() { stepRule.step.neoDeploy(script: nullScript, neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', size: 'lite', ], deployMode: 'warParams', warAction: 'deploy', source: warArchiveName) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy") .hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'trialuser123') .hasSingleQuotedOption('application', 'testApp') .hasSingleQuotedOption('runtime', 'neo-javaee6-wp') .hasSingleQuotedOption('runtime-version', '2\\.125') .hasSingleQuotedOption('size', 'lite') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*\\.war')) } @Test void warFileParamsDeployModeRollingUpdateTest() { shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.* status .*', 'Status: STARTED') stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warParams', warAction: 'rolling-update', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', size: 'lite' ] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh rolling-update") .hasSingleQuotedOption('host', 'test\\.deploy\\.host\\.com') .hasSingleQuotedOption('account', 'trialuser123') .hasSingleQuotedOption('application', 'testApp') .hasSingleQuotedOption('runtime', 'neo-javaee6-wp') .hasSingleQuotedOption('runtime-version', '2\\.125') .hasSingleQuotedOption('size', 'lite') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*\\.war')) } @Test void warFirstTimeRollingUpdateTest() { shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.* status .*', 'ERROR: Application [testApp] not found') stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warParams', warAction: 'rolling-update', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125' ] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher() .hasProlog("neo.sh deploy") .hasSingleQuotedOption('application', 'testApp')) } void warNotStartedRollingUpdateTest() { shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.* status .*', 'Status: STOPPED') stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warParams', warAction: 'rolling-update', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125' ] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher() .hasProlog("\"/opt/neo/tools/neo.sh\" deploy") .hasSingleQuotedOption('application', 'testApp')) } @Test void showLogsOnFailingDeployment() { thrown.expect(AbortException) shellRule.setReturnValue(Type.REGEX, '.* deploy .*', {throw new AbortException()}) stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warParams', warAction: 'deploy', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125' ] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("cat /var/log/neo/*")) } @Test void warPropertiesFileDeployModeTest() { stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warPropertiesFile', warAction: 'deploy', neo: [ propertiesFile: warPropertiesFileName ] ) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy") .hasArgument('war.properties') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*\\.war')) } @Test void warPropertiesFileDeployModeRollingUpdateTest() { shellRule.setReturnValue(JenkinsShellCallRule.Type.REGEX, '.* status .*', 'Status: STARTED') stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warPropertiesFile', warAction: 'rolling-update', neo: [ propertiesFile: warPropertiesFileName ]) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh rolling-update") .hasArgument('war.properties') .hasSingleQuotedOption('user', 'defaultUser') .hasSingleQuotedOption('password', '\\*\\*\\*\\*\\*\\*\\*\\*') .hasSingleQuotedOption('source', '.*\\.war')) } @Test void illegalDeployModeTest() { thrown.expect(Exception) thrown.expectMessage("Invalid deployMode = 'illegalMode'. Valid 'deployMode' values are: [mta, warParams, warPropertiesFile].") stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'illegalMode', warAction: 'deploy', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', size: 'lite' ]) } @Test void illegalWARActionTest() { thrown.expect(Exception) thrown.expectMessage("Invalid warAction = 'illegalWARAction'. Valid 'warAction' values are: [deploy, rolling-update].") stepRule.step.neoDeploy(script: nullScript, source: warArchiveName, deployMode: 'warParams', warAction: 'illegalWARAction', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', size: 'lite' ]) } @Test void dontSwallowExceptionWhenUnableToProvideLogsTest() { thrown.expect(AbortException) thrown.expectMessage('The execution of the deploy command failed, see the log for details.') thrown.expect(new BaseMatcher() { def expectedException = AbortException def expectedText = 'Cannot provide logs.' boolean matches(def ex) { def suppressed = ex.getSuppressed() return (suppressed.size() == 1 && suppressed[0] in expectedException && suppressed[0].message == expectedText) } void describeTo(Description d) { d.appendText(" a suppressed ${expectedException} with message ${expectedText}.") } }) loggingRule.expect('Unable to provide the logs.') helper.registerAllowedMethod('fileExists', [String], { f -> f == 'archive.mtar' } ) helper.registerAllowedMethod("sh", [String], { cmd -> if (cmd.toString().contains('cat logs/neo/')) throw new AbortException('Cannot provide logs.') if (cmd.toString().contains('neo.sh deploy-mta')) throw new AbortException('Something went wrong during neo deployment.') } ) stepRule.step.neoDeploy(script: nullScript, source: archiveName, neo:[credentialsId: 'myCredentialsId'], deployMode: 'mta', utils: utils, ) } @Test void deployModeAsGStringTest() { Map deployProps = [deployMode: 'warPropertiesFile'] stepRule.step.neoDeploy(script: nullScript, utils: utils, neo: [credentialsId: 'myCredentialsId', propertiesFile: warPropertiesFileName], deployMode: "$deployProps.deployMode", source: archiveName) } @Test public void DefaultMavenDeploymentModuleNoPomTest() { thrown.expect(AbortException) thrown.expectMessage(containsString('does not contain a pom file')) nullScript.commonPipelineEnvironment.configuration = [:] stepRule.step.neoDeploy(script: nullScript, deployMode: 'warPropertiesFile') } @Test public void DefaultMavenDeploymentModuleTest() { helper.registerAllowedMethod('readMavenPom', [Map], { return [artifactId:'artifact', packaging: 'war'] }) fileExistsRule.registerExistingFile('./pom.xml') fileExistsRule.registerExistingFile('./target/artifact.war') nullScript.commonPipelineEnvironment.configuration = [:] stepRule.step.neoDeploy([ script: nullScript, deployMode: 'warParams', neo: [ application: 'testApp', runtime: 'neo-javaee6-wp', runtimeVersion: '2.125', host: 'host', account: 'account' ] ]) Assert.assertThat(shellRule.shell, new CommandLineMatcher().hasProlog("neo.sh deploy") .hasSingleQuotedOption('source', './target/artifact.war')) } }