diff --git a/Jenkinsfile b/Jenkinsfile index a8dd9beb..4f02aca4 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,79 +1,101 @@ -node { - try { - notifySlack() +#!groovy - stage('Preparation') { - mvnHome = tool 'm2' - host = "https://assertible.com/deployments" - } +@Library('slackNotifications-shared-library@master') _ - stage('Unit testing') { - sh "curl -u apikey: 'https://assertible.com/deployments' -d'{\"service\":\"d8d73-b0a94b325ae4\",\"environmentName\":\"production\",\"version\":\"v1\"}'" - } - stage('Policy-Code Analysis') { - // Run the maven build - env.NODEJS_HOME = "${tool 'nodejs'}" - env.PATH = "${env.NODEJS_HOME}/bin:${env.PATH}" - sh "npm -v" - sh "apigeelint -s /usr/lib/node_modules/npm/apigee-ci-deploy-bdd-lint-master/hr-api/apiproxy/ -f table.js" - } - - stage('Promotion') { - timeout(time: 2, unit: 'DAYS') { - input 'Do you want to Approve?' - } - } - stage('Deploy to Production') { - // Run the maven build - sh "'${mvnHome}/bin/mvn' -f /usr/lib/node_modules/npm/apigee-ci-deploy-bdd-lint-master/hr-api/pom.xml install -Pprod -Dusername= -Dpassword=" - } - try { - stage('Integration Tests') { - // Run the maven build - env.NODEJS_HOME = "${tool 'nodejs'}" - env.PATH = "${env.NODEJS_HOME}/bin:${env.PATH}" - // Copy the features to npm directory in case of cucumber not found error - //sh "cp $WORKSPACE/hr-api/test/features/prod_tests.feature /usr/lib/node_modules/npm" - sh "cd /usr/lib/node_modules/npm && cucumber-js --format json:reports.json features/prod_tests.feature" - } - } catch (e) { - //if tests fail, I have used an shell script which has 3 APIs to undeploy, delete current revision & deploy previous revision - sh "$WORKSPACE/undeploy.sh" - throw e - } finally { - // generate cucumber reports in both Test Pass/Fail scenario - // to generate reports, cucumber plugin searches for an *.json file in Workspace by default - sh "cd /usr/lib/node_modules/npm && yes | cp -rf reports.json /var/lib/jenkins/workspace/apigee-devops" - - } - } catch (e) { - currentBuild.result = 'FAILURE' - throw e - } finally { - notifySlack(currentBuild.result) - } -} +pipeline { + agent any + tools { + maven 'M2' + jdk 'JDK' + nodejs 'NODEJS' + } + /*environment { + stable_revision_number = 9 + }*/ + stages { + stage('Initial-Checks') { + steps { + sendNotifications 'STARTED' + bat "npm -v" + bat "mvn -v" + echo "$apigeeUsername" + //echo "$stable_revision_number" + } + } + stage('Policy-Code Analysis') { + steps { + bat "npm install -g apigeelint" + bat "apigeelint -s HR-API/apiproxy/ -f codeframe.js" + } + } + stage('Unit-Test-With-Coverage') { + steps { + script { + try { + bat "npm install" + bat "npm test test/unit/*.js" + bat "npm run coverage test/unit/*.js" + } catch (e) { + throw e + } finally { + bat "cd coverage && cp cobertura-coverage.xml $WORKSPACE" + step([$class: 'CoberturaPublisher', coberturaReportFile: 'cobertura-coverage.xml']) + } + } + } + } + /*stage('Promotion') { + steps { + timeout(time: 2, unit: 'DAYS') { + input 'Do you want to Approve?' + } + } + }*/ + stage('Deploy to Production') { + steps { + bat "sh && sh deploy.sh" + //bat "mvn -f HR-API/pom.xml install -Pprod -Dusername=${apigeeUsername} -Dpassword=${apigeePassword} -Dapigee.config.options=update" + } + } + stage('Integration Tests') { + steps { + script { + try { + // using credentials.sh to get the client_id and secret of the app.. + // thought of using them in cucumber oauth feature + // bat "sh && sh credentials.sh" + bat "cd $WORKSPACE/test/integration && npm install" + bat "cd $WORKSPACE/test/integration && npm test" + } catch (e) { + //if tests fail, I have used an shell script which has 3 APIs to undeploy, delete current revision & deploy previous stable revision + bat "sh && sh undeploy.sh" + throw e + } finally { + // generate cucumber reports in both Test Pass/Fail scenario + bat "cd $WORKSPACE/test/integration && cp reports.json $WORKSPACE" + cucumber fileIncludePattern: 'reports.json' + build job: 'cucumber-report' + } + } + } + } + } -def notifySlack(String buildStatus = 'STARTED') { - // Build status of null means success. - cucumber '**/*.json' - buildStatus = buildStatus ? : 'SUCCESS' - - def color - - if (buildStatus == 'STARTED') { - color = '#636363' - } else if (buildStatus == 'SUCCESS') { - color = '#47ec05' - } else if (buildStatus == 'UNSTABLE') { - color = '#d5ee0d' - } else { - color = '#ec2805' - } + post { + always { + // cucumberSlackSend channel: 'apigee-cicd', json: '$WORKSPACE/reports.json' + sendNotifications currentBuild.result + } + } +} - def msg = "${buildStatus}: `${env.JOB_NAME}` #${env.BUILD_NUMBER}:\n${env.BUILD_URL}" +/* - slackSend(color: color, message: msg) -} \ No newline at end of file +using shared library for slack reporting + the lib groovy script must be placed in a vars folder in SCM +using build job to call a Freestyle project which sends the Cucumber reports to slack + currently the cucumberSlackSend channel: 'apigee-cicd', json: '$WORKSPACE/reports.json' + option doesnt send the reports to Slack +*/ \ No newline at end of file diff --git a/credentials.sh b/credentials.sh new file mode 100644 index 00000000..b8c131a2 --- /dev/null +++ b/credentials.sh @@ -0,0 +1,9 @@ +client_id=$(curl -H "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/onlineman477-eval/apiproducts/Cicd-Prod-Product?query=list&entity=keys") + +id=$(jq -r .[0] <<< "${client_id}" ) +echo $id + +client_secret=$(curl -H "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/onlineman477-eval/developers/hr@api.com/apps/hrapp/keys/$id") + +secret=$(jq -r .consumerSecret <<< "${client_secret}" ) +echo $secret \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..ee98c323 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,21 @@ +#! /bin/bash + +# this scripts checks for any new update on edge.json. + # if there is a update it is going to deploy both the config and proxy + # if there is NO update, only proxy will be deployed + + # this script was written by Kurt Googler Kanaskie + # https://community.apigee.com/questions/70453/separating-proxy-and-config-deployment-using-maven.html + +ConfigChanges=`git diff --name-only HEAD HEAD~1 | grep "edge.json"` +if [[ $? -eq 0 ]] +then + export EdgeConfigOptions="update" +else + export EdgeConfigOptions="none" +fi + +# Redirect output from this script to an "edge.properties" file in Jenkins. +echo EdgeConfigOptions=$EdgeConfigOptions + +mvn -f HR-API/pom.xml install -Pprod -Dusername=${apigeeUsername} -Dpassword=${apigeePassword} -Dapigee.config.options=$EdgeConfigOptions \ No newline at end of file diff --git a/hr-api/apiproxy/hr-api.xml b/hr-api/apiproxy/hr-api.xml index 9f7d3341..8174c093 100644 --- a/hr-api/apiproxy/hr-api.xml +++ b/hr-api/apiproxy/hr-api.xml @@ -1,23 +1,38 @@ - + /hr - 1525792835438 + 1562271590046 onlineman477@gmail.com - + SILENTSHADOW$ git: e148adec SilentShadow hr-api - 1525864693839 + 1562687096831 onlineman477@gmail.com - SHA-512:3a2c92ee2de43af8664bb78684297277f4009ba903192bfac059dc2e57d4c6b2dc46accaca487d8488f77b96c9470ab1ca4d51f5916054ba61f3909835a7561b + SHA-512:5a8b8e393941bd33f9de90bb8e2e959dd0af84369ee4247b06c9ccf814cda7c3a7a1fdab47e8f28e91e851894df59de30e0485f813b034ff05c3832210241939 + AM-CustomErrorResponse + AM-PrivateKey + AM-Response + AM-Setting-New-Payload AS-Remove-URIPath Assign-Message-1 - OAuth-v20-1 + EV-Extracting-Payload + JS-Logging-User-Setting-ID + JTP-Too-Long-String + Key-Value-Map-Operations-Credentials + OAuth-Verify + REP-Checking-JSON-Payload + RF-Invalid-UserID + RF-Missing-Phone-Number + Response-Cache-CICD + Verify-JWT default - + + jsc://Logging-User-Setting-ID.js + diff --git a/hr-api/apiproxy/hr-api_rev31_2019_07_03.xml b/hr-api/apiproxy/hr-api_rev31_2019_07_03.xml new file mode 100644 index 00000000..aa1902b0 --- /dev/null +++ b/hr-api/apiproxy/hr-api_rev31_2019_07_03.xml @@ -0,0 +1,33 @@ + + + /hr + + 1562173347052 + onlineman477@gmail.com + SILENTSHADOW$ git: e148adec SilentShadow + hr-api + 1562173347052 + onlineman477@gmail.com + SHA-512:63957310ff6fbad21b6689d33034fddee98f6ab9b3839cb9e76d7d210e64759d5dc76f7ba6fe9365cd68b8006b19d57d29c5d627799b4e089a61618fbab2aac5 + + AM-Setting-New-Payload + AS-Remove-URIPath + Assign-Message-1 + EV-Extracting-Payload + JS-Logging-User-Setting-ID + Key-Value-Map-Operations-Credentials + OAuth-Verify + Response-Cache-CICD + + + default + + + jsc://Logging-User-Setting-ID.js + + + + + default + + diff --git a/hr-api/apiproxy/manifests/manifest.xml b/hr-api/apiproxy/manifests/manifest.xml index 9f319c76..336754db 100644 --- a/hr-api/apiproxy/manifests/manifest.xml +++ b/hr-api/apiproxy/manifests/manifest.xml @@ -1,16 +1,31 @@ - - - + + + + + + + + + + + + + + + + - + - + + + - + diff --git a/hr-api/apiproxy/policies/AM-CustomErrorResponse.xml b/hr-api/apiproxy/policies/AM-CustomErrorResponse.xml new file mode 100644 index 00000000..231c9349 --- /dev/null +++ b/hr-api/apiproxy/policies/AM-CustomErrorResponse.xml @@ -0,0 +1,17 @@ + + + AM-CustomErrorResponse + + + + + { + "error":"string length exceed more than 15 characters" + } + + 400 + Bad Request + + true + + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/AM-PrivateKey.xml b/hr-api/apiproxy/policies/AM-PrivateKey.xml new file mode 100644 index 00000000..624b6566 --- /dev/null +++ b/hr-api/apiproxy/policies/AM-PrivateKey.xml @@ -0,0 +1,13 @@ + + + AM-PrivateKey + + + + private.key + cicd + + + true + + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/AM-Response.xml b/hr-api/apiproxy/policies/AM-Response.xml new file mode 100644 index 00000000..2254ddf3 --- /dev/null +++ b/hr-api/apiproxy/policies/AM-Response.xml @@ -0,0 +1,14 @@ + + + AM-Response + + + + { + "msg":"Record deleted" + } + + + true + + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/AM-Setting-New-Payload.xml b/hr-api/apiproxy/policies/AM-Setting-New-Payload.xml new file mode 100644 index 00000000..ddfd6add --- /dev/null +++ b/hr-api/apiproxy/policies/AM-Setting-New-Payload.xml @@ -0,0 +1,22 @@ + + + AM-Setting-New-Payload + + + +
{kvm-username}
+
{kvm-password}
+
+ + { + "id":"{js-id}", + "name":"{js-name}", + "phone":"{extracted-phone}", + "user":"{extracted-user-type}", + "email":"{extracted-email}", + "account":"{js-account}", + "date": "{system.time}" + } + +
+
\ No newline at end of file diff --git a/hr-api/apiproxy/policies/Assign-Message-1.xml b/hr-api/apiproxy/policies/Assign-Message-1.xml index c14cb7e5..ee094f68 100644 --- a/hr-api/apiproxy/policies/Assign-Message-1.xml +++ b/hr-api/apiproxy/policies/Assign-Message-1.xml @@ -1,46 +1,11 @@ Assign Message-1 - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - name - - - true \ No newline at end of file diff --git a/hr-api/apiproxy/policies/EV-Extracting-Payload.xml b/hr-api/apiproxy/policies/EV-Extracting-Payload.xml new file mode 100644 index 00000000..78b73c38 --- /dev/null +++ b/hr-api/apiproxy/policies/EV-Extracting-Payload.xml @@ -0,0 +1,25 @@ + + + EV-Extracting-Payload + + + true + + + $.user + + + $.phone + + + $.email + + + $.first_name + + + $.last_name + + + request + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/JS-Logging-User-Setting-ID.xml b/hr-api/apiproxy/policies/JS-Logging-User-Setting-ID.xml new file mode 100644 index 00000000..18b432bd --- /dev/null +++ b/hr-api/apiproxy/policies/JS-Logging-User-Setting-ID.xml @@ -0,0 +1,6 @@ + + + JS-Logging-User-Setting-ID + + jsc://Logging-User-Setting-ID.js + diff --git a/hr-api/apiproxy/policies/JTP-Too-Long-String.xml b/hr-api/apiproxy/policies/JTP-Too-Long-String.xml new file mode 100644 index 00000000..d553b85c --- /dev/null +++ b/hr-api/apiproxy/policies/JTP-Too-Long-String.xml @@ -0,0 +1,7 @@ + + + JTP-Too-Long-String + + request + 15 + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/Key-Value-Map-Operations-Credentials.xml b/hr-api/apiproxy/policies/Key-Value-Map-Operations-Credentials.xml new file mode 100644 index 00000000..34eddc94 --- /dev/null +++ b/hr-api/apiproxy/policies/Key-Value-Map-Operations-Credentials.xml @@ -0,0 +1,18 @@ + + + Key-Value-Map-Operations-Credentials + + false + 300 + + + username + + + + + password + + + environment + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/OAuth-Verify.xml b/hr-api/apiproxy/policies/OAuth-Verify.xml new file mode 100644 index 00000000..7076c255 --- /dev/null +++ b/hr-api/apiproxy/policies/OAuth-Verify.xml @@ -0,0 +1,11 @@ + + + OAuth-Verify + + + false + VerifyAccessToken + + + + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/REP-Checking-JSON-Payload.xml b/hr-api/apiproxy/policies/REP-Checking-JSON-Payload.xml new file mode 100644 index 00000000..82763c6c --- /dev/null +++ b/hr-api/apiproxy/policies/REP-Checking-JSON-Payload.xml @@ -0,0 +1,13 @@ + + + REP-Checking-JSON-Payload + + false + + + $.user + ^(rooster|hen)$ + + + request + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/RF-Invalid-UserID.xml b/hr-api/apiproxy/policies/RF-Invalid-UserID.xml new file mode 100644 index 00000000..cb5a1df7 --- /dev/null +++ b/hr-api/apiproxy/policies/RF-Invalid-UserID.xml @@ -0,0 +1,18 @@ + + + RF-Invalid-UserID + + + + + + { + "error":"invlaid id no record found" + } + + 404 + Not Found + + + true + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/RF-Missing-Phone-Number.xml b/hr-api/apiproxy/policies/RF-Missing-Phone-Number.xml new file mode 100644 index 00000000..b3498424 --- /dev/null +++ b/hr-api/apiproxy/policies/RF-Missing-Phone-Number.xml @@ -0,0 +1,18 @@ + + + RF-Missing-Phone-Number + + + + + + { + "error":"missing phone details from payload" + } + + 911 + OMG Number Missing + + + true + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/Response-Cache-CICD.xml b/hr-api/apiproxy/policies/Response-Cache-CICD.xml new file mode 100644 index 00000000..dfd57cfa --- /dev/null +++ b/hr-api/apiproxy/policies/Response-Cache-CICD.xml @@ -0,0 +1,14 @@ + + + Response-Cache-CICD + + + + + + cicd-cache + Exclusive + (request.verb != "GET") + (request.verb != "GET") + true + \ No newline at end of file diff --git a/hr-api/apiproxy/policies/Verify-JWT.xml b/hr-api/apiproxy/policies/Verify-JWT.xml new file mode 100644 index 00000000..23233866 --- /dev/null +++ b/hr-api/apiproxy/policies/Verify-JWT.xml @@ -0,0 +1,14 @@ + + + Verify-JWT + HS256 + + + + cicd + self + proxy,apickli + + github.com/sidd-harth/apigee-cicd + + \ No newline at end of file diff --git a/hr-api/apiproxy/proxies/default.xml b/hr-api/apiproxy/proxies/default.xml index 14c5c772..507788e5 100644 --- a/hr-api/apiproxy/proxies/default.xml +++ b/hr-api/apiproxy/proxies/default.xml @@ -1,9 +1,20 @@ - + + + + AM-CustomErrorResponse + + fault.name = "ExecutionFailed" + + - + + + Response-Cache-CICD + + @@ -13,39 +24,83 @@ - - - - (proxy.pathsuffix MatchesPath "/{name}") and (request.verb = "GET") + + + + RF-Invalid-UserID + (response.content is "null") + + + (proxy.pathsuffix MatchesPath "/{id}") and (request.verb = "GET") - + + + JTP-Too-Long-String + (request.content != null) + + + EV-Extracting-Payload + (request.content != null) + + + RF-Missing-Phone-Number + (extracted-phone is null) + + + JS-Logging-User-Setting-ID + + + Key-Value-Map-Operations-Credentials + + + AM-Setting-New-Payload + + (proxy.pathsuffix MatchesPath "/") and (request.verb = "POST") - + + + JTP-Too-Long-String + (request.content != null) + + + AM-PrivateKey + + + Verify-JWT + + + Assign-Message-1 + + - (proxy.pathsuffix MatchesPath "/{name}") and (request.verb = "PUT") + (proxy.pathsuffix MatchesPath "/{id}") and (request.verb = "PUT") - OAuth-v20-1 + OAuth-Verify Assign-Message-1 - - (proxy.pathsuffix MatchesPath "/{name}") and (request.verb = "DELETE") + + + AM-Response + + + (proxy.pathsuffix MatchesPath "/{id}") and (request.verb = "DELETE") - /hr + /hr22 default secure diff --git a/hr-api/apiproxy/resources/jsc/Logging-User-Setting-ID.js b/hr-api/apiproxy/resources/jsc/Logging-User-Setting-ID.js new file mode 100644 index 00000000..d7ca2a03 --- /dev/null +++ b/hr-api/apiproxy/resources/jsc/Logging-User-Setting-ID.js @@ -0,0 +1,38 @@ +try { + var extracted_user_type = context.getVariable('extracted-user-type'); + var extracted_phone = context.getVariable('extracted-phone'); + var extracted_first_name = context.getVariable('extracted-first-name'); + var extracted_last_name = context.getVariable('extracted-last-name'); + var full_name = extracted_first_name + " " + extracted_last_name; + var ID = extracted_phone.concat(extracted_first_name); + + + var userPayload = { + id: ID, + name: full_name, + type: extracted_user_type, + notifications: (extracted_user_type == "employee") + }; +//print(JSON.stringify(userPayload)); + if (userPayload.notifications) { + userPayload.account = "Active"; + }else{ + userPayload.account = "Inactive"; + } +//print(JSON.stringify(userPayload)); +//print(userPayload.account); + var vRequest = new Request( + 'http://user-logging-request-to-internal-portal.com/a', + 'POST', + {'Content-Type': 'application/json'}, + JSON.stringify(userPayload) + ); + httpClient.send(vRequest); + context.setVariable("js-id",ID); + context.setVariable("js-name",full_name); + context.setVariable("js-account",userPayload.account); +} catch (e) {} + + + +//copy features folder to .nodemodule/.bin && cucu,ber \ No newline at end of file diff --git a/hr-api/apiproxy/targets/default.xml b/hr-api/apiproxy/targets/default.xml index 3858583e..a2c45fc5 100644 --- a/hr-api/apiproxy/targets/default.xml +++ b/hr-api/apiproxy/targets/default.xml @@ -3,17 +3,23 @@ - - + - + + + Response-Cache-CICD + + - https://apibaas-trial.apigee.net/siddharth1/sandbox/employees + + + + /db/users \ No newline at end of file diff --git a/hr-api/config.json b/hr-api/config.json index 38b9d45f..55a20006 100644 --- a/hr-api/config.json +++ b/hr-api/config.json @@ -1,36 +1,16 @@ { "configurations": [ { - "name": "test", + "name": "prod", "policies": [], "proxies": [], - "targets": [ - { - "name": "default.xml", - "tokens": [ - { - "xpath": "/TargetEndpoint/HTTPTargetConnection/URL", - "value": "https://apibaas-trial.apigee.net/siddharth1/sandbox/employees" - } - ] - } - ] + "targets": [] }, { - "name": "prod", + "name": "test", "policies": [], "proxies": [], - "targets": [ - { - "name": "default.xml", - "tokens": [ - { - "xpath": "/TargetEndpoint/HTTPTargetConnection/URL", - "value": "https://apibaas-trial.apigee.net/siddharth1/sandbox/employees" - } - ] - } - ] + "targets": [] } ] } \ No newline at end of file diff --git a/hr-api/edge.json b/hr-api/edge.json new file mode 100644 index 00000000..27e25557 --- /dev/null +++ b/hr-api/edge.json @@ -0,0 +1,113 @@ +{ + "version": "1.0", + "envConfig": { + "prod": { + "targetServers": [ + { + "name" : "cicd-target-server", + "host" : "mamillarevathi-eval-prod.apigee.net", + "isEnabled" : true, + "port" : 80 + } + ], + "virtualHosts": [], + "caches": [ + { + "name": "cicd-cache", + "description": "Cicd pipeline cache in Production", + "expirySettings": { + "timeoutInSec": { + "value": "6000" + }, + "valuesNull": false + } + } + ], + "kvms": [ + { + "name": "cicd-kvm-credentials", + "entry": [ + { + "name": "username", + "value": "aegon" + }, + { + "name": "password", + "value": "hcl" + } + ] + } + ], + "resourcefiles":[], + "flowhooks":[] + } + }, + "orgConfig": { + "apiProducts": [ + { + "name": "Cicd-Prod-Product", + "apiResources": [ + "/", + "/**" + ], + "approvalType": "auto", + "attributes": [ + { + "name": "description", + "value": "Testing CICD" + } + ], + "description": "Testing CICD", + "displayName": "Cicd-Prod-Product", + "environments": [ + "prod" + ], + "proxies": [ + "HR-API", + "CiCd-Security" + ], + "quota": "100000000", + "quotaInterval": "1", + "quotaTimeUnit": "minute", + "scopes": [] + } + ], + "developers": [ + { + "email": "hr@api.com", + "firstName": "HR", + "lastName": "JOHN", + "userName": "JohnSeen", + "attributes": [] + } + ], + "developerApps": { + "hr@api.com": [ + { + "name": "hrapp", + "apiProducts": [ "Cicd-Prod-Product" ], + "callbackUrl": "http://hr.com", + "scopes": [] + } + ] + } + }, + "apiConfig": { + "HR-API": { + "maskconfigs": [ + { + "jSONPathsRequest": [ + "$.phone" + ], + "jSONPathsResponse": [ + "$.email" + ], + "name": "default", + "variables": [ + "response.header.Content-Type" + ] + } + ] + } + } +} \ No newline at end of file diff --git a/hr-api/pom.xml b/hr-api/pom.xml index 1f45f3ef..f5c7aba4 100644 --- a/hr-api/pom.xml +++ b/hr-api/pom.xml @@ -1,110 +1,17 @@ - - - - parent-pom - apigee - 1.0 - ../shared-pom.xml - + + + + apigee + parent-pom + 1.0 + ../shared-pom.xml + + 4.0.0 + Apigee + HR-API + 1.0 + HR-API + pom + - 4.0.0 - apigee - hr-api - 1.0 - hr-api - pom - - diff --git a/hr-api_rev1_test_env.zip b/hr-api_rev1_test_env.zip deleted file mode 100644 index 70f2f1f8..00000000 Binary files a/hr-api_rev1_test_env.zip and /dev/null differ diff --git a/Sequence_Flow.png b/images/Sequence_Flow.png similarity index 100% rename from Sequence_Flow.png rename to images/Sequence_Flow.png diff --git a/images/apickli.jpg b/images/apickli.jpg new file mode 100644 index 00000000..7c13571a Binary files /dev/null and b/images/apickli.jpg differ diff --git a/images/apickli2.jpg b/images/apickli2.jpg new file mode 100644 index 00000000..3abedeec Binary files /dev/null and b/images/apickli2.jpg differ diff --git a/images/apigeelint.jpg b/images/apigeelint.jpg new file mode 100644 index 00000000..3da948a8 Binary files /dev/null and b/images/apigeelint.jpg differ diff --git a/images/apigeelint2.jpg b/images/apigeelint2.jpg new file mode 100644 index 00000000..a7397aff Binary files /dev/null and b/images/apigeelint2.jpg differ diff --git a/images/cobertura.jpg b/images/cobertura.jpg new file mode 100644 index 00000000..31b003ff Binary files /dev/null and b/images/cobertura.jpg differ diff --git a/images/cobertura2.jpg b/images/cobertura2.jpg new file mode 100644 index 00000000..753ed47f Binary files /dev/null and b/images/cobertura2.jpg differ diff --git a/images/cobertura3.jpg b/images/cobertura3.jpg new file mode 100644 index 00000000..e84408ed Binary files /dev/null and b/images/cobertura3.jpg differ diff --git a/images/cucumber1.jpg b/images/cucumber1.jpg new file mode 100644 index 00000000..c175630b Binary files /dev/null and b/images/cucumber1.jpg differ diff --git a/images/cucumber2.jpg b/images/cucumber2.jpg new file mode 100644 index 00000000..7ae63056 Binary files /dev/null and b/images/cucumber2.jpg differ diff --git a/images/cucumber3.jpg b/images/cucumber3.jpg new file mode 100644 index 00000000..600c76a8 Binary files /dev/null and b/images/cucumber3.jpg differ diff --git a/images/cucumber4.jpg b/images/cucumber4.jpg new file mode 100644 index 00000000..920b1cdd Binary files /dev/null and b/images/cucumber4.jpg differ diff --git a/images/cucumber5.jpg b/images/cucumber5.jpg new file mode 100644 index 00000000..97d33e3a Binary files /dev/null and b/images/cucumber5.jpg differ diff --git a/images/cucumber6.jpg b/images/cucumber6.jpg new file mode 100644 index 00000000..171fc24f Binary files /dev/null and b/images/cucumber6.jpg differ diff --git a/images/cucumber7.jpg b/images/cucumber7.jpg new file mode 100644 index 00000000..2da8237d Binary files /dev/null and b/images/cucumber7.jpg differ diff --git a/images/flow.png b/images/flow.png new file mode 100644 index 00000000..082900c9 Binary files /dev/null and b/images/flow.png differ diff --git a/images/istanbul coverage.jpg b/images/istanbul coverage.jpg new file mode 100644 index 00000000..d2e4dfe9 Binary files /dev/null and b/images/istanbul coverage.jpg differ diff --git a/images/mocha.jpg b/images/mocha.jpg new file mode 100644 index 00000000..c5b19599 Binary files /dev/null and b/images/mocha.jpg differ diff --git a/package.json b/package.json new file mode 100644 index 00000000..613b7238 --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "mocha-tests-demo", + "version": "1.0.0", + "description": "a demo for running mocha tests", + "scripts": { + "test": "mocha --reporter mocha-xunit-reporter --reporter dot", + "coverage": "nyc --reporter html --reporter cobertura --reporter text npm test" + }, + "author": "", + "license": "ISC", + "dependencies": { + "JSONPath": "^0", + "is-my-json-valid": "^2", + "path": "^0", + "prettyjson": "^1", + "request": "^2", + "swagger-tools": "^0", + "xmldom": "^0", + "xpath.js": "^1", + "apickli": "latest" + }, + "devDependencies": { + "chai": "^4.1.2", + "expect.js": "*", + "mocha": "^4.0.1", + "mocha-xunit-reporter": "^1.1.0", + "nyc": "^14.1.1", + "rewire": "*", + "sinon": "*", + "cucumber": "^5", + "eslint": "^5", + "eslint-config-google": "^0", + "cucumber-html-reporter": "^4", + "fs-extra": "^7", + "argv": "^0" + } +} diff --git a/revision.sh b/revision.sh new file mode 100644 index 00000000..0cf1e492 --- /dev/null +++ b/revision.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +revision_info=$(curl -H "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/onlineman477-eval/apis/HR-API/deployments") + +previous_revision_number=$(jq -r .environment[0].revision[0].name <<< "${revision_info}" ) + +echo $previous_revision_number diff --git a/shared-pom.xml b/shared-pom.xml index 26f14f52..aa0d7448 100644 --- a/shared-pom.xml +++ b/shared-pom.xml @@ -1,22 +1,5 @@ - - + + 4.0.0 apigee parent-pom @@ -36,30 +19,22 @@ - - ${project.basedir} - - + - - maven-clean-plugin - 2.5 - - maven-resources-plugin - 2.6 + 2.3 - package + copy-resources-step + prepare-package copy-resources true - UTF-8 - ${basedir}/target/apiproxy @@ -73,10 +48,10 @@ io.apigee.build-tools.enterprise4g apigee-edge-maven-plugin - 1.1.6 + 1.1.7 - configure-bundle + configure-bundle-step package configure @@ -91,59 +66,125 @@ + + com.apigee.edge.config + apigee-config-maven-plugin + 1.3.2 + + + create-config-cache + verify + + caches + + + + create-config-targetserver + verify + + targetservers + + + + create-config-kvm + install + + keyvaluemaps + + + + create-config-resourcefiles + install + + resourcefiles + + + + create-config-apiproduct + install + + apiproducts + + + + create-config-developer + install + + developers + + + + create-config-app + install + + apps + + + + create-config-maskconfig + install + + maskconfigs + + + + + - - + test - onlineman477-eval - validate test - test https://api.enterprise.apigee.com v1 - ${org} + onlineman477-eval + prod ${username} ${password} - ${options} - ${revision} - ${tokenurl} + basic + https://login.apigee.com/oauth/token ${mfatoken} - ${authtype} - ${bearer} - ${refresh} ${clientId} ${clientSecret} - - + ${bearer} + ${refresh} prod - onlineman477-eval - validate prod - prod https://api.enterprise.apigee.com v1 - ${org} + onlineman477-eval + prod ${username} ${password} - ${options} - ${revision} - ${tokenurl} + oauth + https://login.apigee.com/oauth/token ${mfatoken} - ${authtype} - ${bearer} - ${refresh} ${clientId} ${clientSecret} - - + ${bearer} + ${refresh} + + + junit + junit + 4.8.2 + test + + + + + + + + + diff --git a/test/integration/apickli/apickli-gherkin.js b/test/integration/apickli/apickli-gherkin.js new file mode 100644 index 00000000..39379dc0 --- /dev/null +++ b/test/integration/apickli/apickli-gherkin.js @@ -0,0 +1,274 @@ +/* eslint new-cap: "off", no-invalid-this: "off" */ + +'use strict'; + +const prettyJson = require('prettyjson'); +const {Before, Given, When, Then} = require('cucumber'); + +const stepContext = {}; + +const prettyPrintJson = function(json) { + const output = { + stepContext, + testOutput: json, + }; + + return prettyJson.render(output, { + noColor: true, + }); +}; + +const callbackWithAssertion = function(callback, assertion) { + if (assertion.success) { + callback(); + } else { + callback(prettyPrintJson(assertion)); + } +}; + +Before(function(scenarioResult, callback) { + // https://github.com/cucumber/cucumber-js/issues/891 + // stepContext.step = step.getName; + // stepContext.scenario = scenario.getName; + + callback(); +}); + +Given(/^I set (.*) header to (.*)$/, function(headerName, headerValue, callback) { + this.apickli.addRequestHeader(headerName, headerValue); + callback(); +}); + +Given(/^I set cookie to (.*)$/, function(cookie, callback) { + this.apickli.addCookie(cookie); + callback(); +}); + +Given(/^I set headers to$/, function(headers, callback) { + this.apickli.setHeaders(headers.hashes()); + callback(); +}); + +Given(/^I set body to (.*)$/, function(bodyValue, callback) { + this.apickli.setRequestBody(bodyValue); + callback(); +}); + +Given(/^I pipe contents of file (.*) to body$/, function(file, callback) { + this.apickli.pipeFileContentsToRequestBody(file, function(error) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +Given(/^I set query parameters to$/, function(queryParameters, callback) { + this.apickli.setQueryParameters(queryParameters.hashes()); + callback(); +}); + +Given(/^I set form parameters to$/, function(formParameters, callback) { + this.apickli.setFormParameters(formParameters.hashes()); + callback(); +}); + +Given(/^I have basic authentication credentials (.*) and (.*)$/, function(username, password, callback) { + this.apickli.addHttpBasicAuthorizationHeader(username, password); + callback(); +}); + +Given(/^I have (.+) client TLS configuration$/, function(configurationName, callback) { + this.apickli.setClientTLSConfiguration(configurationName, function(error) { + if (error) { + callback(new Error(error)); + } + callback(); + }); +}); + +When(/^I GET (.*)$/, function(resource, callback) { + this.apickli.get(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +When(/^I POST to (.*)$/, function(resource, callback) { + this.apickli.post(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +When(/^I PUT (.*)$/, function(resource, callback) { + this.apickli.put(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +When(/^I DELETE (.*)$/, function(resource, callback) { + this.apickli.delete(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +When(/^I PATCH (.*)$/, function(resource, callback) { + this.apickli.patch(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +When(/^I request OPTIONS for (.*)$/, function(resource, callback) { + this.apickli.options(resource, function(error, response) { + if (error) { + callback(new Error(error)); + } + + callback(); + }); +}); + +Then(/^response header (.*) should exist$/, function(header, callback) { + const assertion = this.apickli.assertResponseContainsHeader(header); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response header (.*) should not exist$/, function(header, callback) { + const assertion = this.apickli.assertResponseContainsHeader(header); + assertion.success = !assertion.success; + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body should be valid (xml|json)$/, function(contentType, callback) { + const assertion = this.apickli.assertResponseBodyContentType(contentType); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response code should be (.*)$/, function(responseCode, callback) { + const assertion = this.apickli.assertResponseCode(responseCode); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response code should not be (.*)$/, function(responseCode, callback) { + const assertion = this.apickli.assertResponseCode(responseCode); + assertion.success = !assertion.success; + callbackWithAssertion(callback, assertion); +}); + +Then(/^response header (.*) should be (.*)$/, function(header, expression, callback) { + const assertion = this.apickli.assertHeaderValue(header, expression); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response header (.*) should not be (.*)$/, function(header, expression, callback) { + const assertion = this.apickli.assertHeaderValue(header, expression); + assertion.success = !assertion.success; + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body should contain (.*)$/, function(expression, callback) { + const assertion = this.apickli.assertResponseBodyContainsExpression(expression); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body should not contain (.*)$/, function(expression, callback) { + const assertion = this.apickli.assertResponseBodyContainsExpression(expression); + assertion.success = !assertion.success; + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body path (.*) should be (((?!of type).*))$/, function(path, value, callback) { + const assertion = this.apickli.assertPathInResponseBodyMatchesExpression(path, value); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body path (.*) should not be (((?!of type).+))$/, function(path, value, callback) { + const assertion = this.apickli.assertPathInResponseBodyMatchesExpression(path, value); + assertion.success = !assertion.success; + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body path (.*) should be of type array$/, function(path, callback) { + const assertion = this.apickli.assertPathIsArray(path); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body path (.*) should be of type array with length (.*)$/, function(path, length, callback) { + const assertion = this.apickli.assertPathIsArrayWithLength(path, length); + callbackWithAssertion(callback, assertion); +}); + +Then(/^response body should be valid according to schema file (.*)$/, function(schemaFile, callback) { + this.apickli.validateResponseWithSchema(schemaFile, function(assertion) { + callbackWithAssertion(callback, assertion); + }); +}); + +Then(/^response body should be valid according to openapi description (.*) in file (.*)$/, function(definitionName, swaggerSpecFile, callback) { + this.apickli.validateResponseWithSwaggerSpecDefinition(definitionName, swaggerSpecFile, function(assertion) { + callbackWithAssertion(callback, assertion); + }); +}); + +Then(/^I store the value of body path (.*) as access token$/, function(path, callback) { + this.apickli.setAccessTokenFromResponseBodyPath(path); + callback(); +}); + +When(/^I set bearer token$/, function(callback) { + this.apickli.setBearerToken(); + callback(); +}); + +Given(/^I store the raw value (.*) as (.*) in scenario scope$/, function(value, variable, callback) { + this.apickli.storeValueInScenarioScope(variable, value); + callback(); +}); + +Then(/^I store the value of response header (.*) as (.*) in global scope$/, function(headerName, variableName, callback) { + this.apickli.storeValueOfHeaderInGlobalScope(headerName, variableName); + callback(); +}); + +Then(/^I store the value of body path (.*) as (.*) in global scope$/, function(path, variableName, callback) { + this.apickli.storeValueOfResponseBodyPathInGlobalScope(path, variableName); + callback(); +}); + +Then(/^I store the value of response header (.*) as (.*) in scenario scope$/, function(name, variable, callback) { + this.apickli.storeValueOfHeaderInScenarioScope(name, variable); + callback(); +}); + +Then(/^I store the value of body path (.*) as (.*) in scenario scope$/, function(path, variable, callback) { + this.apickli.storeValueOfResponseBodyPathInScenarioScope(path, variable); + callback(); +}); + +Then(/^value of scenario variable (.*) should be (.*)$/, function(variableName, variableValue, callback) { + if (this.apickli.assertScenarioVariableValue(variableName, variableValue)) { + callback(); + } else { + callback(new Error('value of variable ' + variableName + ' isn\'t equal to ' + variableValue)); + } +}); diff --git a/test/integration/apickli/apickli.js b/test/integration/apickli/apickli.js new file mode 100644 index 00000000..86bf3176 --- /dev/null +++ b/test/integration/apickli/apickli.js @@ -0,0 +1,487 @@ +'use strict'; + +const request = require('request'); +const jsonPath = require('JSONPath'); +const select = require('xpath.js'); +const Dom = require('xmldom').DOMParser; +const fs = require('fs'); +const path = require('path'); +const jsonSchemaValidator = require('is-my-json-valid'); +const spec = require('swagger-tools').specs.v2; + +let accessToken; +const globalVariables = {}; +const _xmlAttributeNodeType = 2; + +const base64Encode = function(str) { + return Buffer.from(str).toString('base64'); +}; + +const getContentType = function(content) { + try { + JSON.parse(content); + return 'json'; + } catch (e) { + try { + new Dom().parseFromString(content); + return 'xml'; + } catch (e) { + return null; + } + } +}; + +const evaluateJsonPath = function(path, content) { + const contentJson = JSON.parse(content); + const evalResult = jsonPath({resultType: 'all'}, path, contentJson); + return (evalResult.length > 0) ? evalResult[0].value : null; +}; + +const evaluateXPath = function(path, content) { + const xmlDocument = new Dom().parseFromString(content); + const node = select(xmlDocument, path)[0]; + if (node.nodeType === _xmlAttributeNodeType) { + return node.value; + } + + return node.firstChild.data; // element or comment +}; + +const evaluatePath = function(path, content) { + const contentType = getContentType(content); + + switch (contentType) { + case 'json': + return evaluateJsonPath(path, content); + case 'xml': + return evaluateXPath(path, content); + default: + return null; + } +}; + +const getAssertionResult = function(success, expected, actual, apickliInstance) { + return { + success, + expected, + actual, + response: { + statusCode: apickliInstance.getResponseObject().statusCode, + headers: apickliInstance.getResponseObject().headers, + body: apickliInstance.getResponseObject().body, + }, + }; +}; + +function Apickli(scheme, domain, fixturesDirectory, variableChar) { + this.domain = scheme + '://' + domain; + this.headers = {}; + this.cookies = []; + this.httpResponse = {}; + this.requestBody = ''; + this.scenarioVariables = {}; + this.fixturesDirectory = (fixturesDirectory ? fixturesDirectory : ''); + this.queryParameters = {}; + this.formParameters = {}; + this.httpRequestOptions = {}; + this.clientTLSConfig = {}; + this.selectedClientTLSConfig = ''; + this.variableChar = (variableChar ? variableChar : '`'); +} + +Apickli.prototype.addRequestHeader = function(name, value) { + name = this.replaceVariables(name); + value = this.replaceVariables(value); + + let valuesArray = []; + if (this.headers[name]) { + valuesArray = this.headers[name].split(','); + } + valuesArray.push(value); + + this.headers[name] = valuesArray.join(','); +}; + +Apickli.prototype.removeRequestHeader = function(name) { + name = this.replaceVariables(name); + delete this.headers[name]; +}; + +Apickli.prototype.setClientTLSConfiguration = function(configurationName, callback) { + if (!this.clientTLSConfig.hasOwnProperty(configurationName)) { + callback('Client TLS Configuration ' + configurationName + ' does not exist.'); + } else { + this.selectedClientTLSConfig = configurationName; + callback(); + } +}; + +Apickli.prototype.setRequestHeader = function(name, value) { + this.removeRequestHeader(name); + + name = this.replaceVariables(name); + value = this.replaceVariables(value); + this.addRequestHeader(name, value); +}; + +Apickli.prototype.getResponseObject = function() { + return this.httpResponse; +}; + +Apickli.prototype.addCookie = function(cookie) { + cookie = this.replaceVariables(cookie); + this.cookies.push(cookie); +}; + +Apickli.prototype.setRequestBody = function(body) { + body = this.replaceVariables(body); + this.requestBody = body; +}; + +Apickli.prototype.setQueryParameters = function(queryParameters) { + const self = this; + const paramsObject = {}; + + queryParameters.forEach(function(q) { + const queryParameterName = self.replaceVariables(q.parameter); + const queryParameterValue = self.replaceVariables(q.value); + paramsObject[queryParameterName] = queryParameterValue; + }); + + this.queryParameters = paramsObject; +}; + +Apickli.prototype.setFormParameters = function(formParameters) { + const self = this; + const paramsObject = {}; + + formParameters.forEach(function(f) { + const formParameterName = self.replaceVariables(f.parameter); + const formParameterValue = self.replaceVariables(f.value); + paramsObject[formParameterName] = formParameterValue; + }); + + this.formParameters = paramsObject; +}; + +Apickli.prototype.setHeaders = function(headersTable) { + const self = this; + + headersTable.forEach(function(h) { + const headerName = self.replaceVariables(h.name); + const headerValue = self.replaceVariables(h.value); + self.addRequestHeader(headerName, headerValue); + }); +}; + +Apickli.prototype.pipeFileContentsToRequestBody = function(file, callback) { + const self = this; + file = this.replaceVariables(file); + fs.readFile(path.join(this.fixturesDirectory, file), 'utf8', function(err, data) { + if (err) { + callback(err); + } else { + self.setRequestBody(data); + callback(); + } + }); +}; + +Apickli.prototype.get = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('GET', resource, callback); +}; + +Apickli.prototype.post = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('POST', resource, callback); +}; + +Apickli.prototype.put = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('PUT', resource, callback); +}; + +Apickli.prototype.delete = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('DELETE', resource, callback); +}; + +Apickli.prototype.patch = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('PATCH', resource, callback); +}; + +Apickli.prototype.options = function(resource, callback) { // callback(error, response) + resource = this.replaceVariables(resource); + this.sendRequest('OPTIONS', resource, callback); +}; + +Apickli.prototype.addHttpBasicAuthorizationHeader = function(username, password) { + username = this.replaceVariables(username); + password = this.replaceVariables(password); + const b64EncodedValue = base64Encode(username + ':' + password); + this.removeRequestHeader('Authorization'); + this.addRequestHeader('Authorization', 'Basic ' + b64EncodedValue); +}; + +Apickli.prototype.assertResponseCode = function(responseCode) { + responseCode = this.replaceVariables(responseCode); + const realResponseCode = this.getResponseObject().statusCode.toString(); + const success = (realResponseCode === responseCode); + return getAssertionResult(success, responseCode, realResponseCode, this); +}; + +Apickli.prototype.assertResponseDoesNotContainHeader = function(header, callback) { + header = this.replaceVariables(header); + const success = typeof this.getResponseObject().headers[header.toLowerCase()] == 'undefined'; + return getAssertionResult(success, true, false, this); +}; + +Apickli.prototype.assertResponseContainsHeader = function(header, callback) { + header = this.replaceVariables(header); + const success = typeof this.getResponseObject().headers[header.toLowerCase()] != 'undefined'; + return getAssertionResult(success, true, false, this); +}; + +Apickli.prototype.assertHeaderValue = function(header, expression) { + header = this.replaceVariables(header); + expression = this.replaceVariables(expression); + const realHeaderValue = this.getResponseObject().headers[header.toLowerCase()]; + const regex = new RegExp(expression); + const success = (regex.test(realHeaderValue)); + return getAssertionResult(success, expression, realHeaderValue, this); +}; + +Apickli.prototype.assertPathInResponseBodyMatchesExpression = function(path, regexp) { + path = this.replaceVariables(path); + regexp = this.replaceVariables(regexp); + const regExpObject = new RegExp(regexp); + const evalValue = evaluatePath(path, this.getResponseObject().body); + const success = regExpObject.test(evalValue); + return getAssertionResult(success, regexp, evalValue, this); +}; + +Apickli.prototype.assertResponseBodyContainsExpression = function(expression) { + expression = this.replaceVariables(expression); + const regex = new RegExp(expression); + const success = regex.test(this.getResponseObject().body); + return getAssertionResult(success, expression, null, this); +}; + +Apickli.prototype.assertResponseBodyContentType = function(contentType) { + contentType = this.replaceVariables(contentType); + const realContentType = getContentType(this.getResponseObject().body); + const success = (realContentType === contentType); + return getAssertionResult(success, contentType, realContentType, this); +}; + +Apickli.prototype.assertPathIsArray = function(path) { + path = this.replaceVariables(path); + const value = evaluatePath(path, this.getResponseObject().body); + const success = Array.isArray(value); + return getAssertionResult(success, 'array', typeof value, this); +}; + +Apickli.prototype.assertPathIsArrayWithLength = function(path, length) { + path = this.replaceVariables(path); + length = this.replaceVariables(length); + let success = false; + let actual = '?'; + const value = evaluatePath(path, this.getResponseObject().body); + if (Array.isArray(value)) { + success = value.length.toString() === length; + actual = value.length; + } + + return getAssertionResult(success, length, actual, this); +}; + +Apickli.prototype.evaluatePathInResponseBody = function(path) { + path = this.replaceVariables(path); + return evaluatePath(path, this.getResponseObject().body); +}; + +Apickli.prototype.setAccessToken = function(token) { + accessToken = token; +}; + +Apickli.prototype.unsetAccessToken = function() { + accessToken = undefined; +}; + +Apickli.prototype.getAccessTokenFromResponseBodyPath = function(path) { + path = this.replaceVariables(path); + return evaluatePath(path, this.getResponseObject().body); +}; + +Apickli.prototype.setAccessTokenFromResponseBodyPath = function(path) { + this.setAccessToken(this.getAccessTokenFromResponseBodyPath(path)); +}; + +Apickli.prototype.setBearerToken = function() { + if (accessToken) { + this.removeRequestHeader('Authorization'); + return this.addRequestHeader('Authorization', 'Bearer ' + accessToken); + } else { + return false; + } +}; + +Apickli.prototype.storeValueInScenarioScope = function(variableName, value) { + this.scenarioVariables[variableName] = value; +}; + +Apickli.prototype.storeValueOfHeaderInScenarioScope = function(header, variableName) { + header = this.replaceVariables(header); // only replace header. replacing variable name wouldn't make sense + const value = this.getResponseObject().headers[header.toLowerCase()]; + this.scenarioVariables[variableName] = value; +}; + +Apickli.prototype.storeValueOfResponseBodyPathInScenarioScope = function(path, variableName) { + path = this.replaceVariables(path); // only replace path. replacing variable name wouldn't make sense + const value = evaluatePath(path, this.getResponseObject().body); + this.scenarioVariables[variableName] = value; +}; + +Apickli.prototype.assertScenarioVariableValue = function(variable, value) { + value = this.replaceVariables(value); // only replace value. replacing variable name wouldn't make sense + return (String(this.scenarioVariables[variable]) === value); +}; + +Apickli.prototype.storeValueOfHeaderInGlobalScope = function(headerName, variableName) { + headerName = this.replaceVariables(headerName); // only replace headerName. replacing variable name wouldn't make sense + const value = this.getResponseObject().headers[headerName.toLowerCase()]; + this.setGlobalVariable(variableName, value); +}; + +Apickli.prototype.storeValueOfResponseBodyPathInGlobalScope = function(path, variableName) { + path = this.replaceVariables(path); // only replace path. replacing variable name wouldn't make sense + const value = evaluatePath(path, this.getResponseObject().body); + this.setGlobalVariable(variableName, value); +}; + +Apickli.prototype.setGlobalVariable = function(name, value) { + globalVariables[name] = value; +}; + +Apickli.prototype.getGlobalVariable = function(name) { + return globalVariables[name]; +}; + +Apickli.prototype.validateResponseWithSchema = function(schemaFile, callback) { + const self = this; + schemaFile = this.replaceVariables(schemaFile, self.scenarioVariables, self.variableChar); + + fs.readFile(path.join(this.fixturesDirectory, schemaFile), 'utf8', function(err, jsonSchemaString) { + if (err) { + callback(err); + } else { + const jsonSchema = JSON.parse(jsonSchemaString); + const responseBody = JSON.parse(self.getResponseObject().body); + + const validate = jsonSchemaValidator(jsonSchema, {verbose: true}); + const success = validate(responseBody); + callback(getAssertionResult(success, validate.errors, null, self)); + } + }); +}; + +Apickli.prototype.validateResponseWithSwaggerSpecDefinition = function(definitionName, swaggerSpecFile, callback) { + const self = this; + swaggerSpecFile = this.replaceVariables(swaggerSpecFile, self.scenarioVariables, self.variableChar); + + fs.readFile(path.join(this.fixturesDirectory, swaggerSpecFile), 'utf8', function(err, swaggerSpecString) { + if (err) { + callback(err); + } else { + const swaggerObject = JSON.parse(swaggerSpecString); + const responseBody = JSON.parse(self.getResponseObject().body); + + spec.validateModel(swaggerObject, '#/definitions/' + definitionName, responseBody, function(err, result) { + if (err) { + callback(getAssertionResult(false, null, err, self)); + } else if (result && result.errors) { + callback(getAssertionResult(false, null, result.errors, self)); + } else { + callback(getAssertionResult(true, null, null, self)); + } + }); + } + }); +}; + +exports.Apickli = Apickli; + +/** + * Replaces variable identifiers in the resource string + * with their value in scope if it exists + * Returns the modified string + * The variable identifiers must be delimited with backticks or variableChar character + * offset defines the index of the char from which the varaibles are to be searched + * It's optional. + * + * Credits: Based on contribution by PascalLeMerrer + */ +Apickli.prototype.replaceVariables = function(resource, scope, variableChar, offset) { + scope = scope || this.scenarioVariables; + variableChar = variableChar || this.variableChar; + offset = offset || 0; + + const startIndex = resource.indexOf(variableChar, offset); + if (startIndex >= 0) { + const endIndex = resource.indexOf(variableChar, startIndex + 1); + if (endIndex > startIndex) { + const variableName = resource.substr(startIndex + 1, endIndex - startIndex - 1); + const variableValue = scope && scope.hasOwnProperty(variableName) ? scope[variableName] : globalVariables[variableName]; + + resource = resource.substr(0, startIndex) + variableValue + resource.substr(endIndex + 1); + resource = this.replaceVariables(resource, scope, variableChar); + } + } + return resource; +}; + +Apickli.prototype.sendRequest = function(method, resource, callback) { + const self = this; + const options = this.httpRequestOptions || {}; + options.url = this.domain + resource; + options.method = method; + options.headers = this.headers; + options.qs = this.queryParameters; + + if (this.requestBody.length > 0) { + options.body = this.requestBody; + } else if (Object.keys(this.formParameters).length > 0) { + options.form = this.formParameters; + } + + const cookieJar = request.jar(); + this.cookies.forEach(function(cookie) { + cookieJar.setCookie(request.cookie(cookie), self.domain); + }); + + options.jar = cookieJar; + + if (this.selectedClientTLSConfig.length > 0) { + options.key = fs.readFileSync(this.clientTLSConfig[this.selectedClientTLSConfig].key); + options.cert = fs.readFileSync(this.clientTLSConfig[this.selectedClientTLSConfig].cert); + if (this.clientTLSConfig[this.selectedClientTLSConfig].ca) { + options.ca = fs.readFileSync(this.clientTLSConfig[this.selectedClientTLSConfig].ca); + } + } + + if (method !== 'OPTIONS') { + options.followRedirect = false; + } + + resource = this.replaceVariables(resource); + request(options, function(error, response) { + if (error) { + return callback(error); + } + + self.httpResponse = response; + callback(null, response); + }); +}; diff --git a/test/integration/apickli/generate-npm.sh b/test/integration/apickli/generate-npm.sh new file mode 100644 index 00000000..6ec4ccbf --- /dev/null +++ b/test/integration/apickli/generate-npm.sh @@ -0,0 +1,14 @@ +#!/bin/sh +rm -rf ./npm +mkdir ./npm + +cp ./apickli.js ./npm/apickli.js +cp ./apickli-gherkin.js ./npm/apickli-gherkin.js +cp ../package.json ./npm/package.json +cp ../../LICENSE ./npm/LICENSE +cp ../../README.md ./npm/README.md + +cd ./npm +npm publish + +rm -rf ../npm diff --git a/test/integration/features/1-basic.feature b/test/integration/features/1-basic.feature new file mode 100644 index 00000000..4de236a3 --- /dev/null +++ b/test/integration/features/1-basic.feature @@ -0,0 +1,29 @@ +@basic-operations +Feature: + Testing the CRUD Operations + + @POST_create_record + Scenario: Create a User Record + Given I set body to {"email":"mail@cicd.com","first_name":"Siddharth","last_name":"Jai","phone":"9886244925","user":"employee"} + And I set headers to + | name | value | + | Content-Type | application/json | + When I POST to /hr22 + Then response code should be 200 + And response body path $.id should be 9886244925Siddharth + And response body path $.name should be Siddharth Jai + And response body path $.account should be Active + + @GET_all_records + Scenario: Read all records and check status code + Given I set Content-type header to application/json + When I GET /hr22 + Then response code should be 200 + + @GET_specific_record + Scenario: retrieve a specific user record + Given I set Content-type header to application/json + When I GET /hr22/9886244925Siddharth + Then response code should be 200 + + diff --git a/test/integration/features/2-error.feature b/test/integration/features/2-error.feature new file mode 100644 index 00000000..71f7bbbd --- /dev/null +++ b/test/integration/features/2-error.feature @@ -0,0 +1,28 @@ +@error-scenarios +Feature: + Testing the Error Scenarios + + @Error_missing_phone + Scenario: error check + Given I set body to {"email":"mail@cicd.com","first_name":"Sidddharth","last_name":"Jai","user":"employee"} + When I POST to /hr22 + Then response code should be 911 + And response body path $.error should be missing phone details from payload + + + @Error_invalid_id + Scenario: Invalid ID returns 404 code response + Given I set Content-type header to application/json + When I GET /hr22/invalid_id + Then response code should be 404 + And response body path $.error should be invlaid id no record found + + @Error_fault_rule + Scenario: Invalid String length returns custom error response + Given I set body to {"email":"mail@cicd.com","first_name":"Sidddharth Barahalikar","last_name":"Jai","phone":"9886244925","user":"employee"} + And I set headers to + | name | value | + | Content-Type | application/json | + When I POST to /hr22 + Then response code should be 400 + And response body path $.error should be string length exceed more than 15 characters \ No newline at end of file diff --git a/test/integration/features/3-security.feature b/test/integration/features/3-security.feature new file mode 100644 index 00000000..64447656 --- /dev/null +++ b/test/integration/features/3-security.feature @@ -0,0 +1,36 @@ +@security-checks +Feature: + Testing Secuirty Policies + + @JWT_security + Scenario: update a record + Given I set Content-type header to application/json + When I POST to /security/jwt + Then response code should be 200 + And I store the value of body path $.jwt as access token + And I set bearer token + When I set body to {"email": "cicd@mail.com"} + And I set headers to + | name | value | + | Content-Type | application/json | + When I PUT /hr22/9886244925Siddharth + Then response code should be 200 + And response body path $.email should be cicd@mail.com + + @OAuth_security + Scenario: delete a record which needs oauth access token + Given I have basic authentication credentials 9HEUGnaUiQGUhMwEflkiIZ9v8kh3fOVc and 5kIH51hVKntKSlHp + When I POST to /security/oauth?grant_type=client_credentials + Then response code should be 200 + And I store the value of body path $.access_token as access token + And I set bearer token + When I DELETE /hr22/9886244926Siddharth + Then response code should be 200 + And response body path $.msg should be Record deleted + + + + + + + diff --git a/test/integration/features/hr-api b/test/integration/features/hr-api new file mode 100644 index 00000000..e654181b --- /dev/null +++ b/test/integration/features/hr-api @@ -0,0 +1,411 @@ +[ + { + "description": " apigee tests", + "keyword": "Feature", + "name": "", + "line": 2, + "id": "", + "tags": [ + { + "name": "@core", + "line": 1 + } + ], + "uri": "prod_tests.feature", + "elements": [ + { + "id": ";create-a-record", + "keyword": "Scenario", + "line": 6, + "name": "Create a record", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@POST_call", + "line": 5 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 7, + "name": "I set body to {\"name\":\"jane\",\"city\":\"hyd\"}", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 8, + "name": "I POST to /hr", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 9, + "name": "response code should be 200", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 10, + "name": "response body path $.entities[0].name should be jane", + "result": { + "status": "undefined" + } + } + ] + }, + { + "id": ";error-check", + "keyword": "Scenario", + "line": 12, + "name": "error check", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@Error", + "line": 11 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 13, + "name": "I set body to {\"name\":\"jane\",\"city\":\"hyd\"}", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 14, + "name": "I POST to /hr", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 15, + "name": "response code should be 400", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 16, + "name": "response body path $.error should be duplicate_unique_property_exists", + "result": { + "status": "undefined" + } + } + ] + }, + { + "id": ";retrieve-a-record", + "keyword": "Scenario", + "line": 18, + "name": "retrieve a record", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@GET_call", + "line": 17 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 19, + "name": "I set Content-type header to application/json", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 20, + "name": "I GET /hr/jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 21, + "name": "response code should be 200", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 22, + "name": "response body path $.entities[0].name should be jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 23, + "name": "response body path $.entities[0].city should be hyd", + "result": { + "status": "undefined" + } + } + ] + }, + { + "id": ";not-found-record", + "keyword": "Scenario", + "line": 25, + "name": "not found record", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@Error", + "line": 24 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 26, + "name": "I set Content-type header to application/json", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 27, + "name": "I GET /hr/invalid_name", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 28, + "name": "response code should be 404", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 29, + "name": "response body path $.error should be entity_not_found", + "result": { + "status": "undefined" + } + } + ] + }, + { + "id": ";update-a-record", + "keyword": "Scenario", + "line": 31, + "name": "update a record", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@PUT_call", + "line": 30 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 32, + "name": "I set body to {\"city\":\"bang\"}", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 33, + "name": "I PUT /hr/jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 34, + "name": "response code should be 200", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 35, + "name": "response body path $.entities[0].name should be jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 36, + "name": "response body path $.entities[0].city should be bang", + "result": { + "status": "undefined" + } + } + ] + }, + { + "id": ";delete-a-record-which-needs-oauth-access-token", + "keyword": "Scenario", + "line": 38, + "name": "delete a record which needs oauth access token", + "tags": [ + { + "name": "@core", + "line": 1 + }, + { + "name": "@OAuth", + "line": 37 + } + ], + "type": "scenario", + "steps": [ + { + "arguments": [], + "keyword": "Given ", + "line": 39, + "name": "I have basic authentication credentials and ", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 40, + "name": "I POST to /hcl_oauth/token?grant_type=client_credentials", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 41, + "name": "response code should be 200", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 42, + "name": "I store the value of body path $.access_token as access token", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 43, + "name": "I set bearer token", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "When ", + "line": 44, + "name": "I DELETE /hr/jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "Then ", + "line": 45, + "name": "response code should be 200", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 46, + "name": "response body path $.entities[0].name should be jane", + "result": { + "status": "undefined" + } + }, + { + "arguments": [], + "keyword": "And ", + "line": 47, + "name": "response body path $.entities[0].city should be bang", + "result": { + "status": "undefined" + } + } + ] + } + ] + } +] \ No newline at end of file diff --git a/test/integration/features/step_definitions/apickli-gherkin.js b/test/integration/features/step_definitions/apickli-gherkin.js new file mode 100644 index 00000000..456f8e55 --- /dev/null +++ b/test/integration/features/step_definitions/apickli-gherkin.js @@ -0,0 +1 @@ +module.exports = require('../../apickli/apickli-gherkin'); diff --git a/test/integration/features/support/init.js b/test/integration/features/support/init.js new file mode 100644 index 00000000..2e64d8a3 --- /dev/null +++ b/test/integration/features/support/init.js @@ -0,0 +1,16 @@ +'use strict'; + +const apickli = require('../../apickli/apickli.js'); +const {defineSupportCode} = require('cucumber'); + + + +defineSupportCode(function({Before}) { + Before(function() { + this.apickli = new apickli.Apickli('http', 'onlineman477-eval-prod.apigee.net'); + this.apickli.addRequestHeader('Cache-Control', 'no-cache'); + }); +}); +defineSupportCode(function({setDefaultTimeout}) { + setDefaultTimeout(60 * 1000); // this is in ms +}); \ No newline at end of file diff --git a/test/integration/package.json b/test/integration/package.json new file mode 100644 index 00000000..db3bdbde --- /dev/null +++ b/test/integration/package.json @@ -0,0 +1,44 @@ +{ + "name": "apickli", + "version": "2.3.2", + "description": "Collection of utility functions and a gherkin framework for REST API integration testing based on cucumber.js", + "main": "apickli.js", + "readme": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/apickli/apickli.git" + }, +"scripts": { + "test": "cucumber-js -f json:reports.json" + }, + "keywords": [ + "cucumber.js", + "integration", + "rest", + "api" + ], + "license": "MIT", + "dependencies": { + "JSONPath": "^0", + "is-my-json-valid": "^2", + "path": "^0", + "prettyjson": "^1", + "request": "^2", + "swagger-tools": "^0", + "xmldom": "^0", + "xpath.js": "^1", + "apickli": "latest" + }, + "devDependencies": { + "cucumber": "^5", + "eslint": "^5", + "eslint-config-google": "^0", + "gulp": "^4", + "cucumber-html-reporter": "^4", + "fs-extra": "^7", + "argv": "^0", + "gulp-cucumber": "^0", + "gulp-eslint": "^5", + "gulp4-run-sequence": "^0" + } +} \ No newline at end of file diff --git a/test/unit/test_log-user.js b/test/unit/test_log-user.js new file mode 100644 index 00000000..c5005d5a --- /dev/null +++ b/test/unit/test_log-user.js @@ -0,0 +1,106 @@ +var expect = require('expect.js'); +var sinon = require('sinon'); + +// this is the javascript file that is under test +var jsFile = '../../HR-API/apiproxy/resources/jsc/Logging-User-Setting-ID.js'; + + +GLOBAL.context = { + getVariable: function(s) {}, + setVariable: function(s) {} +}; + +GLOBAL.httpClient = { + send: function(s) {} +}; + +GLOBAL.Request = function(s) {}; + +var contextGetVariableMethod, contextSetVariableMethod; +var httpClientSendMethod; +var requestConstructor; + +// This method will execute before every it() method in the test +// we are stubbing all Apigee objects and the methods we need here +beforeEach(function () { + contextGetVariableMethod = sinon.stub(context, 'getVariable'); + contextSetVariableMethod = sinon.stub(context, 'setVariable'); + requestConstructor = sinon.spy(GLOBAL, 'Request'); + httpClientSendMethod = sinon.stub(httpClient, 'send'); +}); + +// restore all stubbed methods back to their original implementation +afterEach(function() { + contextGetVariableMethod.restore(); + contextSetVariableMethod.restore(); + requestConstructor.restore(); + httpClientSendMethod.restore(); +}); + +// this is the Loggly test feature here +describe('feature: user account creation', function() { + it('should send inactive account response', function() { + contextGetVariableMethod.withArgs('extracted-user-type').returns('customer'); + contextGetVariableMethod.withArgs('extracted-phone').returns('9886244926'); + contextGetVariableMethod.withArgs('extracted-first-name').returns('Siddharth'); + contextGetVariableMethod.withArgs('extracted-last-name').returns('B'); + + + var errorThrown = false; + try { requireUncached(jsFile);} catch (e) { errorThrown = true; } + + expect(errorThrown).to.equal(false); + + expect(httpClientSendMethod.calledOnce).to.be.true; + expect(requestConstructor.calledOnce).to.be.true; + + var requestConstructorArgs = requestConstructor.args[0]; + expect(requestConstructorArgs[0]).to.equal('http://user-logging-request-to-internal-portal.com/a'); + expect(requestConstructorArgs[1]).to.equal('POST'); + expect(requestConstructorArgs[2]['Content-Type']).to.equal('application/json'); + + var userPayloadObject = JSON.parse(requestConstructorArgs[3]); + expect(userPayloadObject.id).to.equal('9886244926Siddharth'); + expect(userPayloadObject.name).to.equal('Siddharth B'); + expect(userPayloadObject.type).to.equal('customer'); + expect(userPayloadObject.notification).to.be.false; + expect(userPayloadObject.account).to.equal('Inactive'); + }); + + +/* + it('should send active account response', function() { + contextGetVariableMethod.withArgs('extracted-user-type').returns('employee'); + contextGetVariableMethod.withArgs('extracted-phone').returns('9886244926'); + contextGetVariableMethod.withArgs('extracted-first-name').returns('Siddharth'); + contextGetVariableMethod.withArgs('extracted-last-name').returns('B'); + + + var errorThrown = false; + try { requireUncached(jsFile);} catch (e) { errorThrown = true; } + + + expect(errorThrown).to.equal(false); + + + expect(httpClientSendMethod.calledOnce).to.be.true; + expect(requestConstructor.calledOnce).to.be.true; + var requestConstructorArgs = requestConstructor.args[0]; + expect(requestConstructorArgs[0]).to.equal('http://user-logging-request-to-internal-portal.com/a'); + expect(requestConstructorArgs[1]).to.equal('POST'); + expect(requestConstructorArgs[2]['Content-Type']).to.equal('application/json'); + var userPayloadObject = JSON.parse(requestConstructorArgs[3]); + expect(userPayloadObject.id).to.equal('9886244926Siddharth'); + expect(userPayloadObject.name).to.equal('Siddharth B'); + expect(userPayloadObject.type).to.equal('employee'); + expect(userPayloadObject.notification).to.be.true; + expect(userPayloadObject.account).to.equal('Active'); + }); */ +}); + +// node.js caches modules that is imported using 'require' +// this utility function prevents caching between it() functions - don't forget that variables are global in our javascript file +function requireUncached(module){ + delete require.cache[require.resolve(module)]; + return require(module); +} diff --git a/undeploy.sh b/undeploy.sh index 5464cd5c..e8d120af 100644 --- a/undeploy.sh +++ b/undeploy.sh @@ -1,22 +1,28 @@ #!/bin/bash -deployment_info=$(curl -H "Authorization: Basic " "https://api.enterprise.apigee.com/v1/organizations/o/apis/api-name/deployments") + +deployment_info=$(curl -H "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/onlineman477-eval/apis/HR-API/deployments") rev_num=$(jq -r .environment[0].revision[0].name <<< "${deployment_info}" ) env_name=$(jq -r .environment[0].name <<< "${deployment_info}" ) api_name=$(jq -r .name <<< "${deployment_info}" ) org_name=$(jq -r .organization <<< "${deployment_info}" ) -declare -r num1=1 -pre_rev=$(expr "$rev_num" - "$num1") +declare -r stable_revision_number=20 +#pre_rev=$(expr "$rev_num" - "$num1") + echo $rev_num echo $api_name echo $org_name echo $env_name echo $pre_rev +echo $stable_revision_number + + +curl -X DELETE --header "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/$org_name/environments/$env_name/apis/$api_name/revisions/$rev_num/deployments" + +curl -X DELETE --header "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/$org_name/apis/$api_name/revisions/$rev_num" -curl -X DELETE --header "Authorization: Basic " "https://api.enterprise.apigee.com/v1/organizations/$org_name/environments/$env_name/apis/$api_name/revisions/$rev_num/deployments" +curl -X POST --header "Content-Type: application/x-www-form-urlencoded" --header "Authorization: Basic $base64encoded" "https://api.enterprise.apigee.com/v1/organizations/$org_name/environments/$env_name/apis/$api_name/revisions/$stable_revision_number/deployments" -curl -X DELETE --header "Authorization: Basic " "https://api.enterprise.apigee.com/v1/organizations/$org_name/apis/$api_name/revisions/$rev_num" -curl -X POST --header "Content-Type: application/x-www-form-urlencoded" --header "Authorization: Basic " "https://api.enterprise.apigee.com/v1/organizations/$org_name/environments/$env_name/apis/$api_name/revisions/$pre_rev/deployments" \ No newline at end of file diff --git a/vars/sendNotifications.groovy b/vars/sendNotifications.groovy new file mode 100644 index 00000000..b4a3918e --- /dev/null +++ b/vars/sendNotifications.groovy @@ -0,0 +1,21 @@ +def call(String buildStatus = 'STARTED') { + // Build status of null means success. + //cucumber '**/*.json' + buildStatus = buildStatus ?: 'SUCCESS' + + def color + + if (buildStatus == 'STARTED') { + color = '#636363' + } else if (buildStatus == 'SUCCESS') { + color = '#47ec05' + } else if (buildStatus == 'UNSTABLE') { + color = '#d5ee0d' + } else { + color = '#ec2805' + } + + def msg = "${buildStatus}: `${env.JOB_NAME}` #${env.BUILD_NUMBER}:\n${env.BUILD_URL}" + + slackSend(color: color, message: msg) +} \ No newline at end of file