A practical way to automate testing Oauth 2.0 Service - Part 2

September 07, 2018 by RestBird

DevOps Integration

In the previous article, we have introduced how to use Restbird task to automate testing OAuth 2.0 service, now we’re going to step forward to discuss how to integrate Restbird test cases into Continuous Integration (CI) system. And show you how Restbird can take an important part in DevOps.

Let’s continue with the Box test project, since we already have a valid access token in hand and a task to refresh it periodically, we can now freely play with the APIs.

In the following example, we’ll create a simple test case in Restbird to implement the logic of “Get id of the specific folder name under root directory, if the folder doesn’t exist, then create a new one. ”, the test case will be marked as success if the logic completed without error, otherwise the case failed.

We will then integrate this test case into Jenkins, the most popular CI tool being used in DevOps teams.

All Restbird code used in this example have been uploaded into Github Repo restbird/example-Box-Oauth2

Create Tets Case for API endpoints

Two BOX File related APIs will be used in this example, we create a new Rest case in Box project and include these 2 APIs

Create API to retrieve id of specific folder

Here is the API definition of Box to get infomation of a folder.

Method GET
Url https://api.box.com/2.0/folders/{folder id}

The root folder of a Box account is always represented by the ID “0”.

This is how the request looks in Restbird

Box Get folder info

Then impletement the logic to retrive folderid of foldername and save folder_id in environment variable in Response Check Scripts, here we use GoLang as sample language:

package api
import "callapi"
import "net/http"
import "io/ioutil"
import	"encoding/json"
import "fmt"

type MyEntry struct {
   Id  string        `json:"id,omitempty"`
   Name string       `json:"name,omitempty"`
   Type string	      `json:"type,omitempty"`
}	

type MyCollection struct{
   Entries []MyEntry	`json:"entries,omitempty"`
}

type MyDATA struct {
   Item_collection  MyCollection        `json:"item_collection,omitempty"`
}

func (c  CallBack) ResponseValidate(resp *http.Response, ctx *callapi.RestBirdCtx) bool {
   var body []byte
   var err error
   var data MyDATA

   if resp.StatusCode == 200 {
       if body, err = ioutil.ReadAll(resp.Body); err != nil {
         fmt.Println("read body failed.")
         return false
       } 

       if err = json.Unmarshal(body, &data); err != nil {
         fmt.Println("conver body to json failed")
         return false
       }	

       for i, v := range data.Item_collection.Entries {
         if v.Name == ctx.GetVars("folder_name") {
           ctx.SetVars("folder_id", v.Id)
           fmt.Println("Found folder: ")
           fmt.Println("i, folder_id, folder_name: " , i, ",", ctx.GetVars("folder_id"), ",", ctx.GetVars("folder_name"))
           return true
         }
       }

       fmt.Println("Can't find folder: ", ctx.GetVars("folder_name"))
       return true
   }
   return false
}

Create API to create specific folder

Here is the API definition of Box to create a folder.

Method POST
Url https://api.box.com/2.0/folders
Req Body
(raw)
{“name”:“{{folder_name}}”, “parent”: {“id”: “0”}}

Here is how the request looks in Restbird

Box create folder

In Response Check Scripts we’ll check the response code and retrieve the folder_id of the new folder and save it into environment variable.

package api
import "callapi"
import "net/http"
import "fmt"
import "io/ioutil"
import	"encoding/json"
type MyDATA struct {
    Id string `json:"id, omitempty"`
}

func (c  CallBack) ResponseValidate(resp *http.Response, ctx *callapi.RestBirdCtx) bool {
    if resp.StatusCode == 201 {
        var body []byte
    	var err error
    	var data MyDATA
    
    	if body, err = ioutil.ReadAll(resp.Body); err != nil {
    		fmt.Println("read body failed.")
    		return false
    	}
    
    	if err = json.Unmarshal(body, &data); err != nil {
    		fmt.Println("conver body to json failed:", err.Error())
    		return false
    	}	    
    	
    	ctx.SetVars("folder_id", data.Id)
    	fmt.Println("folder_id", ctx.GetVars("folder_id"))

        return true
    }
	return false
}

All Rest APIs need to add OAuth Authorization header with access token Authorization : Bearer {{box_access_token}}

Create Test script to implement logic

Beside Http(s) request, Restbird also supports creating pure script in rest project to organize multiple single APIs into complex test cases. Here we’re going to create a script for our first test case:

  • Case 0: Get Id of folder_name, if folder doesn’t exist, create a new one

TestScript

In the script, we call the 2 APIs we just created in previous steps.

package api
import "callapi"
import "fmt"
import api0 "restbird/Box/File/api0" 
import api1 "restbird/Box/File/api1"

type CallBack struct {}

func (c  CallBack) GoScripts(ctx *callapi.RestBirdCtx) bool{
    
    var folder_id = ""
    ctx.SetVars("folder_name", "Demo6")
    ctx.SetVars("folder_id", "")
    
    reterr, retbool,retMsg := callapi.DoHttpRequest("Box/File", "api0", api0.CallBack{},  ctx)
    
    fmt.Println(reterr, retbool,retMsg)
    
    if !retbool {
        fmt.Println("callapi failed", retMsg)
        return false
    }
    
    folder_id = ctx.GetVars("folder_id")
    
    if folder_id == ""{
        fmt.Println("Folder doesn't exist, create a new one: ", ctx.GetVars("folder_name"))
        reterr, retbool,retMsg := callapi.DoHttpRequest("Box/File", "api1", api1.CallBack{},  ctx)
        fmt.Println(reterr, retbool,retMsg)
        
        if(!retbool){
          fmt.Println("callapi failed", retMsg)
          return false
        }
    
        folder_id = ctx.GetVars("folder_id")
    }
    
     fmt.Println(folder_id)
     
     return true
}

We can always run API or script directly in Restbird to check logic, e.g. now after click “Run Test” we can check “console log” to verify that everything runs properly.

TestScript-console

Integrate with Jenkins

Now we have everything ready on Restbird side, the final step is to integrate Restbird into Jenkins to complete our CI system.

Well, this is truely as easy as breeze, with Http Request plugin, Jenkins is able to call Restbird API

  • Install Http Request plugin in Jenkins

Manage Jenkins -> Manage Plugin -> Search “Http Request” then install

  • Install Pipeline Utility Steps plugin in Jenkins

Manage Jenkins -> Manage Plugin -> Search “Pipeline Utility Steps” then install

This plugin provide lib for Json parser

  • Add new pipeline task

Here we create a typical CI work flow including 3 steps: build -> deploy -> run auto test, Restbird is used in auto test step. This code can be used to define pipeline sript:

node {
    def payload 

    stage('Build') {
    //...
    }
    stage('Deploy') {
    //..
    }
    stage('Run test') {
        def host = 'http://192.168.1.178:10000'
        def basicAuth = 'Basic YWRtaW46YWRtaW4='
      
        println('Call Restbird API to run test:')
        def reqBody = '{"casepath":"Box/TestScripts","apis":["api0"]}'
        def response = httpRequest httpMode:'POST', customHeaders: [[name: 'Authorization', value: basicAuth]], requestBody: reqBody, url:host+"/v1/rest/run"
        println('Status: '+response.status)
        println('Response: '+response.content)
        payload = readJSON text: response.content
        def historyId = payload.his.id
        println('History_id: '+ historyId)
        
        def historyReqBody = '{"project":"Box/TestScripts","id":"' + payload.his.id + '", "immediatereturn": true}'
   
        for(int i=0;i<10;i++){
            println('Call Restbird API to get result: '  + i)

            def historyResponse = httpRequest httpMode:'POST', customHeaders: [[name: 'Authorization', value: basicAuth]], requestBody: historyReqBody, url:host+"/v1/rest/runresult"
            println('Status: '+historyResponse.status)
            println('Response: '+historyResponse.content)
          
            
              if(historyResponse.status == 200){
                  
                printConsoleLog(host, basicAuth, historyId)
                
                payload = readJSON text: historyResponse.content
                if(payload.code == 0){
                    if(payload.his.responseval.result == true){
                         currentBuild.result = 'SUCCESS'
                    }else{
                         currentBuild.result = 'FAILURE'
                    }
                    return
                }else if(payload.code == -1){
                    println("Test unfinshed, check back in 10 seconds")
                    sleep 10
                }else{
                    println("Test error" + payload.code + ", " + payload.info)
                    currentBuild.result = 'FAILURE'
                    return
                }
            }else{
                currentBuild.result = 'FAILURE'
                return
            }

        }
        
        //return timeout
        currentBuild.result = 'FAILURE'
        return
    }
}
def printConsoleLog(host, basicAuth, historyId){
   println('--Call Restbird API to get console log:')
   def consoleResponse = httpRequest httpMode:'GET', customHeaders: [[name: 'Authorization', value: basicAuth]], url:host+"/v1/rest/his/console?project=Box/TestScripts&id=" + historyId
   println('--Console Status: '+consoleResponse.status)
   println('--Console Response: '+consoleResponse.content)  
}

*checkout Restbird API definition

Download the example project from github