implement feature to generate random test data
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-01-11 16:14:00 +11:00
parent aa65b1e784
commit 2afdacaa78
11 changed files with 500 additions and 15 deletions

View File

@@ -9,8 +9,8 @@ export CGO_ENABLED=0
commit=$(git rev-parse HEAD)
#tag=$(git describe --tags --abbrev=0)
buildtime=$(TZ=Australia/Sydney date +%Y-%m-%dT%T%z)
#git_version=$(git describe --tags --always --long --dirty)
package_name=api-tester
git_version=$(git describe --tags --always --long --dirty)
package_name=apitester
#echo "Current directory:"
#pwd
@@ -35,7 +35,7 @@ do
exit 1
fi
echo "build complete : $output_name"
sha256sum build/$output_name > build/${output_name}_checksum.txt
#sha256sum build/$output_name > build/${output_name}_checksum.txt
#ls -lah ./build/
done

View File

@@ -38,7 +38,58 @@
}
}
}
}
},
"test3": {
"path": "/api/admin/group/add",
"disabled": true,
"method": "POST",
"description": "add new local group",
"body": {
"groupName": "test local group",
"ldapGroup": false
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
},
"test4": {
"path": "/api/admin/groups",
"method": "GET",
"description": "get group listing again",
"expect": {
"header": {
"contains": {
"http_status": "200"
}
},
"body": {
"hasKeys": ["groupName"],
"contains": {
"message": "success"
}
}
}
},
"test5": {
"path": "/api/admin/group/delete",
"method": "POST",
"description": "remove new local group",
"body": {
"groupName": "test local group",
"ldapGroup": false
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
}
},
"url": "https://10.63.39.130:8443",
"insecure": true,

View File

@@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
"github.com/iancoleman/orderedmap"
@@ -145,20 +146,36 @@ func ReadInput(data []byte, testDefinitions *TestDefinitions) {
}
// Body
if val, ok := thisTestCaseMap.Get("body"); ok {
// Create a normal string map for all the body key-value pairs
// TODO : Support nested json instead of single level flat request
//thisTestCase.Body = OrderedToStringMap(val.(orderedmap.OrderedMap))
// Turn the original string into json
bytes, err := json.Marshal(val)
if err != nil {
error := fmt.Sprintf("Error mnarshalling request body for test case : '%s'\n", err)
panic(error)
}
// Now that we have json, convert it to an interface that we can play with
var jsonData interface{}
err = json.Unmarshal(bytes, &jsonData)
if err != nil {
error := fmt.Sprintf("Error processing request body for test case : '%s'\n", err)
panic(error)
} else {
thisTestCase.Body = bytes
//fmt.Printf("Body marshalled:\n%v\n", string(bytes))
}
// Search for any keys in the body that we need to replace with a dynamic random value
results := findRandom(nil, jsonData)
// for debugging
//resultJSON, _ := json.MarshalIndent(results, "", " ")
//fmt.Println(string(resultJSON))
// store the results back into the body
newBytes, err := json.Marshal(results)
if err != nil {
error := fmt.Sprintf("Error turning body interface back into json for test case : '%s'\n", err)
panic(error)
}
thisTestCase.Body = newBytes
//fmt.Printf("Body marshalled:\n%v\n", string(newBytes))
}
// Header
if val, ok := thisTestCaseMap.Get("header"); ok {
@@ -190,6 +207,47 @@ func ReadInput(data []byte, testDefinitions *TestDefinitions) {
}
}
// Use this function to find and generate any random values we want to use during testing
func findRandom(key interface{}, data interface{}) interface{} {
switch v := data.(type) {
case map[string]interface{}:
if randomValue, ok := v["random"]; ok {
if randomObj, ok := randomValue.(map[string]interface{}); ok {
if isString, isStringOk := randomObj["isString"].(bool); isStringOk && isString {
if length, lengthOk := randomObj["length"].(float64); lengthOk {
newString := generateRandomString(int(length))
fmt.Printf("Generated random string '%s' to insert into JSON body for key '%s'\n", newString, key.(string))
return newString
}
} else if isInt, isIntOk := randomObj["isInt"].(bool); isIntOk && isInt {
newInt := rand.Int()
fmt.Printf("Generated random number '%d' to insert into JSON body\n", newInt)
return newInt
}
}
}
for key, value := range v {
v[key] = findRandom(key, value)
}
case []interface{}:
for i, value := range v {
v[i] = findRandom(i, value)
}
}
return data
}
func generateRandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result := make([]byte, length)
for i := range result {
result[i] = charset[rand.Intn(len(charset))]
}
return string(result)
}
func ReadHeaderTestCases(input orderedmap.OrderedMap, result *HeaderTests) {
result.Contains = make(map[string]string)
result.Equals = make(map[string]string)

36
main.go
View File

@@ -4,7 +4,9 @@ import (
"flag"
"fmt"
"os"
"reflect"
"strconv"
"strings"
"github.com/iancoleman/orderedmap"
)
@@ -12,6 +14,36 @@ import (
var testDefinitions *TestDefinitions
var captureValues *CaptureValues
func PrintStructContents(s interface{}, indentLevel int) string {
var result strings.Builder
val := reflect.ValueOf(s)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
indent := strings.Repeat("\t", indentLevel)
result.WriteString(fmt.Sprintf("%s%s: ", indent, fieldType.Name))
switch field.Kind() {
case reflect.Struct:
result.WriteString("\n")
result.WriteString(PrintStructContents(field.Interface(), indentLevel+1))
default:
result.WriteString(fmt.Sprintf("%v\n", field.Interface()))
}
}
return result.String()
}
func main() {
var s []byte
var err error
@@ -43,6 +75,8 @@ func main() {
// Process the input json
ReadInput(s, testDefinitions)
//fmt.Println(PrintStructContents(testDefinitions.TestCases[1], 0))
// Perform specified test cases
for i := range testDefinitions.TestCases {
@@ -52,6 +86,7 @@ func main() {
}
RunTest(&testDefinitions.TestCases[i])
fmt.Printf("\nRunning checks on output\n")
success, err := CheckResults(&testDefinitions.TestCases[i])
if err != nil {
@@ -71,6 +106,7 @@ func main() {
// For debugging, just dump the output
//fmt.Printf("%+v\n", testDefinitions)
//fmt.Println(PrintStructContents(testDefinitions, 0))
}
// fileExists returns true if the specified file exists and is not a directory

75
permissions-test.json Normal file
View File

@@ -0,0 +1,75 @@
{
"name": "CBS permissions testing",
"testCases": {
"test1": {
"path": "/api/login",
"method": "POST",
"description": "Perform login",
"body": {
"username": "Administrator",
"password": "Password123"
},
"expect": {
"header": {
"contains": {
"http_status": "200",
"Content-Type": "application/json"
}
},
"body": {
"hasKeys": ["access_token"]
}
}
},
"test2": {
"path": "/api/admin/permissions",
"method": "GET",
"description": "get list of permissions",
"expect": {
"header": {
"contains": {
"http_status": "200"
}
},
"body": {
"hasKeys": ["permissionId"],
"contains": {
"message": "success"
}
}
}
},
"test3": {
"path": "/api/admin/permission/add",
"method": "POST",
"description": "create new permission",
"body": {
"Description": "Readonly access to default safe",
"safeId": 1,
"userId": 2,
"readOnly": true
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
}
},
"url": "https://10.63.39.130:8443",
"insecure": true,
"header": {
"Content-Type": "application/json",
"Authorization": "Bearer %ACCESS_TOKEN%"
},
"capture": {
"test1": {
"body": {
"access_token": "%ACCESS_TOKEN%"
}
}
}
}

View File

@@ -128,12 +128,14 @@ func RunTest(testCase *TestCase) error {
// If we found one, add the appropriate replacement to captureValues
for i := range testDefinitions.CaptureCases {
if testDefinitions.CaptureCases[i].TestCaseName == testCase.Name {
fmt.Printf("\nProcessing capture(s) for test case '%s'\n", testCase.Name)
captureData := testDefinitions.CaptureCases[i].CaptureData
for k, v := range captureData.Body.Data {
//fmt.Printf("Body capture %s = %s\n", k, v)
if val, ok := testCase.ResultBodyMap[k]; ok {
fmt.Printf("Found capture %s\n", k)
// TODO handle values other than string using a switch
//fmt.Printf("Found matching capture in body with value : '%v', storing replacement with key '%s'\n", val, v)
captureValues.Data[v] = val.(string)

120
safes-test.json Normal file
View File

@@ -0,0 +1,120 @@
{
"name": "CBS safes testing",
"testCases": {
"test1": {
"path": "/api/login",
"method": "POST",
"description": "Perform login",
"body": {
"username": "Administrator",
"password": "Password123"
},
"expect": {
"header": {
"contains": {
"http_status": "200",
"Content-Type": "application/json"
}
},
"body": {
"hasKeys": ["access_token"]
}
}
},
"test2": {
"path": "/api/safe/list",
"method": "GET",
"description": "get user allowed safe listing",
"expect": {
"header": {
"contains": {
"http_status": "200"
}
},
"body": {
"hasKeys": ["safeName"],
"contains": {
"message": "success"
}
}
}
},
"test3": {
"path": "/api/admin/safe/add",
"method": "POST",
"description": "create new safe",
"body": {
"safeName": "API test created safe"
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
},
"test4": {
"path": "/api/admin/safe/add",
"method": "POST",
"description": "create new safe",
"body": {
"safeName": "API test created second safe"
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
},
"test5": {
"path": "/api/admin/safe/listall",
"method": "GET",
"description": "get all defined safe listing",
"expect": {
"header": {
"contains": {
"http_status": "200"
}
},
"body": {
"hasKeys": ["safeName"],
"contains": {
"message": "success"
}
}
}
},
"test6": {
"path": "/api/admin/safe/delete",
"method": "POST",
"description": "get all defined safe listing",
"body": {
"safeName": "API test created safe"
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
}
},
"url": "https://10.63.39.130:8443",
"insecure": true,
"header": {
"Content-Type": "application/json",
"Authorization": "Bearer %ACCESS_TOKEN%"
},
"capture": {
"test1": {
"body": {
"access_token": "%ACCESS_TOKEN%"
}
}
}
}

View File

@@ -28,6 +28,11 @@ type TestCase struct {
ResultBodyMap map[string]interface{}
}
type RandomBody struct {
IsString bool `json:"isString"`
Length int `json:"length"`
}
type CaptureCase struct {
TestCaseName string
CaptureData CaptureCaseData

67
test-random.json Normal file
View File

@@ -0,0 +1,67 @@
{
"name": "CBS test cases",
"testCases": {
"test1": {
"path": "/api/login",
"method": "POST",
"description": "Perform login",
"body": {
"username": "Administrator",
"password": "Password123"
},
"expect": {
"header": {
"contains": {
"http_status": "200",
"Content-Type": "application/json"
}
},
"body": {
"hasKeys": ["access_token"]
}
}
},
"test12": {
"path": "/api/secret/store",
"method": "POST",
"description": "store first new secret",
"body": {
"safeId": 1,
"deviceName": {
"random": {
"isString": true,
"length": 12
}
},
"deviceCategory": "appliance",
"userName": "service@cdc.home",
"secretValue": {
"random": {
"isString": true,
"length": 20
}
}
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
}
},
"url": "https://10.63.39.130:8443",
"insecure": true,
"header": {
"Content-Type": "application/json",
"Authorization": "Bearer %ACCESS_TOKEN%"
},
"capture": {
"test1": {
"body": {
"access_token": "%ACCESS_TOKEN%"
}
}
}
}

View File

@@ -22,7 +22,7 @@
}
},
"test2": {
"path": "/api/admin/user/register",
"path": "/api/admin/user/add",
"method": "POST",
"description": "register user account",
"header": {
@@ -136,7 +136,12 @@
"description": "store first new secret",
"body": {
"safeId": 1,
"deviceName": "avcp01.cdc.home",
"deviceName": {
"random": {
"isString": true,
"length": 12
}
},
"deviceCategory": "appliance",
"userName": "service@cdc.home",
"secretValue": "TheWiggles"

66
user-test.json Normal file
View File

@@ -0,0 +1,66 @@
{
"name": "CBS normal user testing",
"testCases": {
"test1": {
"path": "/api/login",
"method": "POST",
"description": "Perform login",
"body": {
"username": "nathtest",
"password": "VMware1!"
},
"expect": {
"header": {
"contains": {
"http_status": "200",
"Content-Type": "application/json"
}
},
"body": {
"hasKeys": ["access_token"]
}
}
},
"test20": {
"path": "/api/secret/list",
"disabled": false,
"method": "GET",
"description": "List secrets",
"header": {
"Authorization": "Bearer %ACCESS_TOKEN%"
},
"expect": {
"header": {
"contains": {
"http_status": "200"
}
}
}
},
"test22": {
"path": "/api/secret/update",
"method": "POST",
"description": "try update existing secret",
"body": {
"deviceName": "adcp01.cdc.home",
"deviceCategory": "windows-server",
"userName": "dummy@cdc.home",
"secretValue": "TheWiggles"
}
}
},
"url": "https://10.63.39.130:8443",
"insecure": true,
"header": {
"Content-Type": "application/json",
"Authorization": "Bearer %ACCESS_TOKEN%"
},
"capture": {
"test1": {
"body": {
"access_token": "%ACCESS_TOKEN%"
}
}
}
}