This commit is contained in:
2026-06-19 17:37:20 +03:00
parent b897b3447a
commit f679261f74
1513 changed files with 107093 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
# delta
AppID="app_id"
AppSecret="app_secret"
SSOURL="https://sso-delta.qa.loc:8443"
DecortURL="https://delta.qa.loc/"

View File

@@ -0,0 +1,151 @@
# Авто тесты при переходе на новую версию платформы
## Содержание
- [Авто тесты при переходе на новую версию платформы](#авто-тесты-при-переходе-на-новую-версию-платформы)
- [Содержание](#содержание)
- [Подготовка к тестам](#подготовка-к-тестам)
- [Тесты Raw методов (Get, List)](#тесты-raw-методов-get-list)
- [Cloudapi](#cloudapi)
- [Cloudbroker](#cloudbroker)
- [SDN](#sdn)
- [Тесты запросов](#тесты-запросов)
- [Cloudapi](#cloudapi-1)
- [Cloudbroker](#cloudbroker-1)
- [SDN](#sdn-1)
- [Тесты API методов](#тесты-api-методов)
## Подготовка к тестам
1. Тесты находятся по директории `decort-sdk/tests/platform_upgrade`
2. Внутри директории нужно создать и заполнить файл `.env` по аналогии с `.env.template` для доступа к платформе
3. Внутри директории нужно создать и заполнить файл `input.json`, содержащий json из метода [POST /system/docgenerator/prepareCatalog](https://delta.qa.loc/portal/#/api/system) (для получения json нажать кнопку Try it Out!) - требуется только для тестов запросов и тестов API методов
Примечание: тесты можно запускать напрямую методами среды разработки либо из командной строки из нужной директории, например командой `go test -v -run <TestName>`, где `<TestName>` - название запускаемого теста.
## Тесты Raw методов (Get, List)
### Cloudapi
Запустить тест `TestGetListCloudAPI` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных полей, проверить, что они содержатся на платформе и не содержатся в go структурах, завести и исправить ошибку.
Пример вывода:
```go
utils_get_list.go:71: Sizes list: OK
utils_get_list.go:71: Tasks list:
Platform has these fields that golang struct doesn't: [updatedBy guid]
utils_get_list.go:71: VINS list:
Platform has these fields that golang struct doesn't: [freeIPs extnetId]
utils_get_list.go:71: VINS get: OK
--- FAIL: TestGetListCloudAPI (66.13s)
```
### Cloudbroker
**ВНИМАНИЕ: из-за особенностей архитектуры моделей (вложенные структуры без json-тегов) в cloudbroker этот тест часто выдает некорректные результаты - ложноположительные на предмет багов.**
Запустить тест `TestGetListCloudbroker` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных полей, проверить, что они содержатся на платформе и не содержатся в go структурах, завести и исправить ошибку.
### SDN
Запустить тест `TestGetListSDNAPI` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных полей, проверить, что они содержатся на платформе и не содержатся в go структурах, завести и исправить ошибку.
Пример вывода аналогичен тестам для Cloudapi и Cloudbroker.
## Тесты запросов
### Cloudapi
Запустить тест `TestRequestsCloudAPI` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных ошибок, проверить, что они являются ошибками (возможны ситуации, когда расхождение платформы и sdk задумано специально), завести и исправить ошибку.
Пример вывода:
```go
=== RUN TestRequestsCloudAPI
utils_requests.go:125: Path /cloudapi/compute/affinityRuleRemove has following errors: [Field value has different required parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudapi/lb/listDeleted has following errors: [Platform has field accountId that golang structure doesn't]
utils_requests.go:125: Path /cloudapi/compute/pfwAdd has following errors: [Field localBasePort has different required parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudapi/kvmppc/create has following errors: [Field interfaces has different type parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudapi/image/create has following errors: [Platform (14) and golang structure (15) have different amount of fields. Field accountId has different required parameters on the platform and in golang structure]
<...>
utils_requests.go:125: Path /cloudapi/compute/affinityRuleAdd has following errors: [Field value has different required parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudapi/compute/antiAffinityRuleAdd has following errors: [Field value has different required parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudapi/lb/create has following errors: [Platform (7) and golang structure (8) have different amount of fields. Field extnetId has different required parameters on the platform and in golang structure Field vinsId has different required parameters on the platform and in golang structure]
--- FAIL: TestRequestsCloudAPI (0.02s)
FAIL
```
### Cloudbroker
Запустить тест `TestRequestsCloudbroker` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных ошибок, проверить, что они являются ошибками (возможны ситуации, когда расхождение платформы и sdk задумано специально), завести и исправить ошибку.
Пример вывода:
```go
=== RUN TestRequestsCloudbroker
utils_requests.go:125: Path /cloudbroker/image/updateNodes has following errors: [Field enabledNodes has different type parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudbroker/lb/listDeleted has following errors: [Platform has field accountId that golang structure doesn't]
utils_requests.go:125: Path /cloudbroker/k8ci/listDeleted has following errors: [Platform has field k8cId that golang structure doesn't]
<...>
utils_requests.go:125: Path /cloudbroker/account/setCpuAllocationRatio has following errors: [Platform has field accountId that golang structure doesn't Field ratio has different required parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudbroker/image/list has following errors: [Field size has different type parameters on the platform and in golang structure]
utils_requests.go:125: Path /cloudbroker/rg/create has following errors: [Platform has field uniqPools that golang structure doesn't]
utils_requests.go:125: Path /cloudbroker/sep/create has following errors: [Field config has different required parameters on the platform and in golang structure]
--- FAIL: TestRequestsCloudbroker (0.02s)
FAIL
```
### SDN
Запустить тест `TestRequestsSDN` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных ошибок, проверить, что они являются ошибками (возможны ситуации, когда расхождение платформы и sdk задумано специально), завести и исправить ошибку.
Пример вывода аналогичен тестам для Cloudapi и Cloudbroker.
## Тесты API методов
Запустить тест `TestGetAllPaths` в `decort-sdk/tests/platform_upgrade/cloud_test.go`
При наличии подсвеченных ошибок, проверить, что указанные методы API не являются устаревшими (deprecated). В противном случае добавить устаревшие методы в переменную `DEPRECATED_GROUPS` по адресу `decort-sdk/tests/platform_upgrade/utils_url.go`.
Пример вывода:
```go
=== RUN TestGetAllPaths
cloud_test.go:692: Below API handlers (30 in total) need to be added to decort-sdk:
/cloudbroker/grid/addCustomBackupPath
/cloudapi/user/getResourceConsumption
/cloudapi/user/authenticate
/cloudbroker/compute/getCustomFields
/cloudapi/user/isValidInviteUserToken
/cloudapi/pcidevice/list
/cloudbroker/compute/createTemplateFromBlank
/cloudbroker/account/listVMs
/cloudapi/user/brief
/cloudapi/user/search
/cloudbroker/node/setMemAllocationRatio
/cloudbroker/image/computeciUnset
/cloudbroker/image/uploadImageFile
/cloudapi/account/getStats
/cloudapi/disks/fromPlatformDisk
/cloudbroker/node/getLogicalCoresCount
/cloudapi/lb/stop
/cloudapi/account/listVMs
/cloudapi/user/setData
/cloudapi/compute/createTemplateFromBlank
/cloudbroker/grid/removeCustomBackupPath
/cloudapi/user/getAudit
/cloudbroker/account/listCS
/cloudapi/account/listCS
/cloudbroker/node/setCpuAllocationRatio
/cloudbroker/grid/setPasswordPolicy
/cloudapi/user/apiList
/cloudbroker/disks/fromPlatformDisk
/cloudapi/user/get
/cloudapi/vgpu/list
--- FAIL: TestGetAllPaths (0.03s)
FAIL
exit status 1
FAIL repository.basistech.ru/BASIS/decort-golang-sdk/tests/platform_upgrade 0.031s
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
go test -v -run TestGetListCloudAPI > TestGetListCloudAPI.txt
go test -v -run TestGetListCloudbroker > TestGetListCloudbroker.txt
go test -v -run TestGetListSDNAPI > TestGetListSDNAPI.txt
go test -v -run TestRequestsCloudAPI > TestRequestsCloudAPI.txt
go test -v -run TestRequestsCloudbroker > TestRequestsCloudbroker.txt
go test -v -run TestRequestsSDN > TestRequestsSDN.txt
go test -v -run TestGetAllPaths > TestGetAllPaths.txt

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
package test
import (
"encoding/json"
"fmt"
"log"
"os"
"reflect"
"strconv"
"testing"
"time"
"github.com/joho/godotenv"
decort "repository.basistech.ru/BASIS/decort-golang-sdk"
"repository.basistech.ru/BASIS/decort-golang-sdk/config"
)
func getClient() (*decort.DecortClient, error) {
cfg := config.Config{
Retries: 5,
SSLSkipVerify: true,
}
err := godotenv.Load(".env")
if err != nil {
log.Fatal("Error loading .env file")
}
appID, ok := os.LookupEnv("AppID")
if !ok {
return &decort.DecortClient{}, fmt.Errorf("AppID is not set")
}
cfg.AppID = appID
AppSecret, ok := os.LookupEnv("AppSecret")
if !ok {
return &decort.DecortClient{}, fmt.Errorf("AppSecret is not set")
}
cfg.AppSecret = AppSecret
SSOURL, ok := os.LookupEnv("SSOURL")
if !ok {
return &decort.DecortClient{}, fmt.Errorf("SSOURL is not set")
}
cfg.SSOURL = SSOURL
DecortURL, ok := os.LookupEnv("DecortURL")
if !ok {
return &decort.DecortClient{}, fmt.Errorf("DecortURL is not set")
}
cfg.DecortURL = DecortURL
cfg.SetTimeout(5 * time.Minute)
c := decort.New(cfg)
return c, nil
}
// getResult checks how json-structure of bytes is different from golang structure and return t.Error if there are any differences.
func getResult(testName string, bytes []byte, structure any, t *testing.T) string {
// convert bytes to map[string]interface{}
bytesMap, err := GetMapFromBytes(bytes)
if err != nil {
t.Errorf("%s: can not unmarshal json", testName)
}
// convert structure to map[string]interface{}
structMap := GetMapFromStructure(structure)
// assert if they are equal
if !reflect.DeepEqual(bytesMap, structMap) {
var result = getDifference(testName, bytesMap, structMap)
t.Errorf("\n%s\n", result)
return result
}
result := fmt.Sprint(testName, ": ok")
t.Log(result)
return result
}
// getDifference tells which fields are present in bytesMap and not present in structMap and vice versa.
func getDifference(testName string, bytesMap, structMap map[string]interface{}) string {
var result string
bytesFields, _ := evaluate(bytesMap, structMap)
// structFields, _ := evaluate(structMap, bytesMap)
result += fmt.Sprintf("%s: ", testName)
if len(bytesFields) > 0 {
result += fmt.Sprintf("\nPlatform has these fields that golang struct doesn't: %v\n", bytesFields)
}
// if len(structFields) > 0 {
// result += fmt.Sprintf("Golang struct has these fields that platform doesn't: %v\n", structFields)
// }
if len(bytesFields) == 0 {
result += "OK"
}
return result
}
func evaluate(m1, m2 map[string]interface{}) ([]string, []string) {
var s1, s2 []string
for k1, v1 := range m1 {
v2, ok := m2[k1]
if !ok {
s1 = append(s1, k1)
continue
}
v1Map := v1.(map[string]interface{})
v2Map := v2.(map[string]interface{})
len1 := len(v1Map)
len2 := len(v2Map)
if len1 == 0 && len2 == 0 {
continue
} else {
toAdd1, toAdd2 := evaluate(v1Map, v2Map)
s1 = append(s1, toAdd1...)
s2 = append(s2, toAdd2...)
}
}
for k2, v2 := range m2 {
v1, ok := m1[k2]
if !ok {
if !contains(s2, k2) {
s2 = append(s2, k2)
}
continue
}
v1Map := v1.(map[string]interface{})
v2Map := v2.(map[string]interface{})
len1 := len(v1Map)
len2 := len(v2Map)
if len1 == 0 && len2 == 0 {
continue
} else {
toAdd1, toAdd2 := evaluate(v1Map, v2Map)
s1 = appendSelectedElements(s1, toAdd1)
s2 = appendSelectedElements(s2, toAdd2)
}
}
return s1, s2
}
func contains(slice []string, element string) bool {
for _, elem := range slice {
if elem == element {
return true
}
}
return false
}
func appendSelectedElements(slice []string, elements []string) []string {
result := make([]string, 0, len(slice))
result = append(result, slice...)
for _, el := range elements {
if !contains(slice, el) {
result = append(result, el)
}
}
return result
}
// GetMapFromBytes converts []byte to map[string]interface{}
func GetMapFromBytes(bytes []byte) (map[string]interface{}, error) {
var unmarshalMaps map[string]interface{}
err := json.Unmarshal(bytes, &unmarshalMaps)
if err != nil {
var unmarshalSlice []interface{}
err = json.Unmarshal(bytes, &unmarshalSlice)
if err != nil {
return nil, err
}
if len(unmarshalSlice) == 0 {
return map[string]interface{}{}, nil
}
t, ok := unmarshalSlice[0].(map[string]interface{})
if !ok {
return nil, err
}
unmarshalMaps = t
}
if err != nil {
return nil, err
}
return getMapBytes(unmarshalMaps), nil
}
func getMapBytes(value map[string]interface{}) map[string]interface{} {
temporary := make(map[string]interface{})
for k, v := range value {
list, ok := v.([]interface{})
if ok && len(list) > 0 {
elem, ok := list[0].(map[string]interface{})
if ok {
temporary[k] = getMapBytes(elem)
continue
}
temporary[k] = map[string]interface{}{}
continue
}
m, ok := v.(map[string]interface{})
if ok {
temporary[k] = getMapBytes(m)
continue
}
if _, err := strconv.Atoi(k); err != nil {
temporary[k] = map[string]interface{}{}
}
}
return temporary
}
// GetMapFromStructure converts golang structure to map[string]interface{}
func GetMapFromStructure(structure any) map[string]interface{} {
typ := reflect.TypeOf(structure)
for typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return getMapStruct(typ, make(map[reflect.Type]bool))
}
func getMapStruct(t reflect.Type, visited map[reflect.Type]bool) map[string]interface{} {
if visited[t] {
return map[string]interface{}{}
}
visited[t] = true
defer func() { visited[t] = false }()
temporary := make(map[string]interface{})
for i := 0; i < t.NumField(); i++ {
tag := t.Field(i).Tag.Get("json")
kind := t.Field(i).Type.Kind()
//nolint
switch kind {
case reflect.Array, reflect.Slice:
elem := t.Field(i).Type.Elem()
switch elem.Kind() {
case reflect.Struct:
temporary[tag] = getMapStruct(elem, visited)
default:
temporary[tag] = map[string]interface{}{}
}
case reflect.Struct:
if tag == "" {
return getMapStruct(t.Field(i).Type, visited)
} else {
temporary[tag] = getMapStruct(t.Field(i).Type, visited)
}
// в этом месте работает некорректно для вложенных структур без json тегов (scope - все в cloudbroker)
case reflect.Map, reflect.Interface, reflect.String, reflect.Bool, reflect.Int64, reflect.Int, reflect.Uint,
reflect.Int32, reflect.Uint64, reflect.Float32, reflect.Float64:
temporary[tag] = map[string]interface{}{}
}
}
return temporary
}
// getMapFromFile converts json to map[string]interface{}
func getMapFromFile(bytes []byte) (map[string]interface{}, error) {
var x map[string]interface{}
err := json.Unmarshal(bytes, &x)
if err != nil {
return nil, err
}
paths := x["paths"].(map[string]interface{})
return paths, nil
}

View File

@@ -0,0 +1,435 @@
package test
import (
"fmt"
"io"
"os"
"reflect"
"strings"
"testing"
)
// extractTypeFromSchema extracts type from schema, handling oneOf cases
// When oneOf contains a type and null, it returns the non-null type
func extractTypeFromSchema(schema map[string]interface{}) string {
// Check for direct type
if paramType, ok := schema["type"].(string); ok {
return paramType
}
// Check for oneOf
if oneOf, ok := schema["oneOf"].([]interface{}); ok {
for _, item := range oneOf {
if itemMap, ok := item.(map[string]interface{}); ok {
if itemType, ok := itemMap["type"].(string); ok {
// Skip null type, return the first non-null type found
if itemType != "null" {
return itemType
}
}
}
}
}
return ""
}
func getParameters(input map[string]interface{}) []interface{} {
var emptySlice []interface{}
methods := []string{"get", "post", "put", "delete", "patch", "head", "options"}
var methodData interface{}
found := false
for _, method := range methods {
if data, ok := input[method]; ok {
methodData = data
found = true
break
}
}
if !found {
return emptySlice
}
parameters, ok := methodData.(map[string]interface{})
if !ok {
return emptySlice
}
var result []interface{}
if requestBody, ok := parameters["requestBody"].(map[string]interface{}); ok {
if content, ok := requestBody["content"].(map[string]interface{}); ok {
// Check for application/x-www-form-urlencoded, application/json, or multipart/form-data
var schemaData map[string]interface{}
var found bool
if formData, ok := content["application/x-www-form-urlencoded"].(map[string]interface{}); ok {
if schema, ok := formData["schema"].(map[string]interface{}); ok {
schemaData = schema
found = true
}
} else if jsonData, ok := content["application/json"].(map[string]interface{}); ok {
if schema, ok := jsonData["schema"].(map[string]interface{}); ok {
schemaData = schema
found = true
}
} else if multipartData, ok := content["multipart/form-data"].(map[string]interface{}); ok {
if schema, ok := multipartData["schema"].(map[string]interface{}); ok {
schemaData = schema
found = true
}
}
if found && schemaData != nil {
if properties, ok := schemaData["properties"].(map[string]interface{}); ok {
requiredFields := make(map[string]bool)
if req, ok := schemaData["required"].([]interface{}); ok {
for _, r := range req {
if reqStr, ok := r.(string); ok {
requiredFields[reqStr] = true
}
}
}
for name, prop := range properties {
propMap, ok := prop.(map[string]interface{})
if !ok {
continue
}
newParam := make(map[string]interface{})
newParam["name"] = name
propType := extractTypeFromSchema(propMap)
if propType != "" {
newParam["type"] = propType
}
newParam["required"] = requiredFields[name]
if propType == "array" {
// Try to get items from propMap first
if items, ok := propMap["items"].(map[string]interface{}); ok {
newParam["items"] = items
} else if oneOf, ok := propMap["oneOf"].([]interface{}); ok {
// If items not in propMap, try to get from oneOf array element
for _, item := range oneOf {
if itemMap, ok := item.(map[string]interface{}); ok {
if itemType, ok := itemMap["type"].(string); ok && itemType == "array" {
if items, ok := itemMap["items"].(map[string]interface{}); ok {
newParam["items"] = items
break
}
}
}
}
}
}
result = append(result, newParam)
}
}
}
}
}
if params, ok := parameters["parameters"].([]interface{}); ok {
for _, p := range params {
param, ok := p.(map[string]interface{})
if !ok {
continue
}
newParam := make(map[string]interface{})
if name, ok := param["name"].(string); ok {
newParam["name"] = name
}
if schema, ok := param["schema"].(map[string]interface{}); ok {
paramType := extractTypeFromSchema(schema)
if paramType != "" {
newParam["type"] = paramType
}
if paramType == "array" {
if items, ok := schema["items"].(map[string]interface{}); ok {
newParam["items"] = items
} else if oneOf, ok := schema["oneOf"].([]interface{}); ok {
for _, item := range oneOf {
if itemMap, ok := item.(map[string]interface{}); ok {
if itemType, ok := itemMap["type"].(string); ok && itemType == "array" {
if items, ok := itemMap["items"].(map[string]interface{}); ok {
newParam["items"] = items
break
}
}
}
}
}
}
}
// Handle required field
if required, ok := param["required"].(bool); ok {
newParam["required"] = required
} else {
newParam["required"] = false
}
result = append(result, newParam)
}
}
if len(result) > 0 {
return result
}
return emptySlice
}
func getBytesFromJSON(fileName string, t *testing.T) []byte {
jsonFile, err := os.Open(fileName)
if err != nil {
t.Error(err)
}
defer jsonFile.Close()
bytes, err := io.ReadAll(jsonFile)
if err != nil {
t.Error(err)
}
return bytes
}
func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
var requests map[string]interface{}
switch cloud {
case "cloudapi":
requests = getRequestsMapCloudAPI()
case "cloudbroker":
requests = getRequestsMapCloudbroker()
case "sdn":
requests = getRequestsMapSDN()
default:
t.Fatalf("Wrong cloud provided, expected `cloudapi`, `cloudbroker` or `sdn`, got %s", cloud)
}
var dataLogs []string
paths, err := getMapFromFile(bytes)
if err != nil {
t.Error(err)
}
i := 0
for k, v := range paths {
// exclude deprecated urls from analysis
if !validateUrlFromJson(k) {
continue
}
params := getParameters(v.(map[string]interface{}))
structure, ok := requests[k]
if !ok {
continue
}
i++
typStruct := reflect.TypeOf(structure)
var errs []string
// empty request case
if len(params) == 0 && structure == nil {
continue
}
if len(params) != typStruct.NumField() {
errs = append(errs, fmt.Sprintf("Platform (%d) and golang structure (%d) have different amount of fields.", len(params), typStruct.NumField()))
}
paramMap := make(map[string]bool)
paramRequiredMap := make(map[string]bool)
for _, p := range params {
param, ok := p.(map[string]interface{})
if !ok {
continue
}
name, ok := param["name"].(string)
if ok {
paramMap[name] = true
required, ok := param["required"].(bool)
if ok {
paramRequiredMap[name] = required
} else {
paramRequiredMap[name] = false
}
}
}
for _, p := range params {
param, ok := p.(map[string]interface{})
if !ok {
continue
}
name, ok := param["name"].(string)
if !ok {
name = ""
}
required, ok := param["required"].(bool)
if !ok {
required = false
}
typ, ok := p.(map[string]interface{})["type"].(string)
if !ok {
typ = ""
}
var items string
if p.(map[string]interface{})["items"] != nil {
itemsTemp := p.(map[string]interface{})["items"]
if itemsTemp != nil {
itemsType := itemsTemp.(map[string]interface{})["type"]
if itemsType != nil {
items, ok = itemsType.(string)
if !ok {
items = ""
}
}
}
}
var found bool
for i := 0; i < typStruct.NumField(); i++ {
jsonTag := typStruct.Field(i).Tag.Get("json")
validation, _ := typStruct.Field(i).Tag.Lookup("validate")
if checkName(name, jsonTag) {
if !checkRequired(required, validation, typ) {
errs = append(errs, fmt.Sprintf("Field %s has different required parameters on the platform and in golang structure", name))
}
if !checkKind(typ, items, typStruct.Field(i).Type) {
errs = append(errs, fmt.Sprintf("Field %s has different type parameters on the platform and in golang structure", name))
}
found = true
break
}
}
if !found {
errs = append(errs, fmt.Sprintf("Platform has field %s that golang structure doesn't", name))
}
}
// Check if required fields in Go structure are missing from platform JSON
// or if they exist but are not required on platform
for i := 0; i < typStruct.NumField(); i++ {
jsonTag := typStruct.Field(i).Tag.Get("json")
validation, _ := typStruct.Field(i).Tag.Lookup("validate")
fieldName := strings.Split(jsonTag, ",")[0]
if fieldName == "" || fieldName == "-" {
continue
}
if strings.Contains(validation, "required") {
if !paramMap[fieldName] {
errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
} else {
platformRequired := paramRequiredMap[fieldName]
if !platformRequired {
fieldType := typStruct.Field(i).Type
if fieldType.Kind() == reflect.Bool {
errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
}
}
}
}
}
if len(errs) > 0 {
msg := fmt.Sprintf("Path %s has following errors: %v", k, errs)
t.Error(msg)
dataLogs = append(dataLogs, msg)
}
}
if len(requests) != i {
msg := fmt.Sprintf("Amount of structure checked (%d) is not the same as amount of platform requests available (%d), please check getRequests func in code.",
i, len(requests))
t.Error(msg)
dataLogs = append(dataLogs, msg)
}
}
// checkName checks if name field from platform has the same value as json tag in golang structure (maybe including omitempty)
func checkName(name, tag string) bool {
return strings.Contains(tag, name)
}
// checkRequired checks if required field from platform has the same value as validate tag in golang structure
func checkRequired(required bool, validation, fieldType string) bool {
if required && strings.Contains(validation, "required") {
return true
}
if required && (!strings.Contains(validation, "omitempty") && validation != "") {
return true
}
if !required && (validation == "" || strings.Contains(validation, "omitempty")) {
return true
}
if fieldType == "boolean" {
return true // otherwise we have issues with setting false/true
}
return false
}
// checkKind checks if type field from platform has the same value as field type in golang structure
func checkKind(platformType, items string, typ reflect.Type) bool {
//nolint
switch typ.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint64, reflect.Uint16, reflect.Uint32, reflect.Uint8:
if platformType == "integer" {
return true
}
return false
case reflect.String:
if platformType == "string" {
return true
}
return false
case reflect.Bool:
if platformType == "boolean" {
return true
}
return false
case reflect.Float32, reflect.Float64:
if platformType == "number" {
return true
}
return false
case reflect.Array, reflect.Slice:
if platformType != "array" {
return false
}
elem := typ.Elem()
switch elem.Kind() {
case reflect.String:
if items == "string" {
return true
}
return false
case reflect.Int, reflect.Int32, reflect.Int64, reflect.Int8, reflect.Int16, reflect.Uint, reflect.Uint64, reflect.Uint16, reflect.Uint32, reflect.Uint8:
if items == "integer" {
return true
}
return false
default: // for cases like dataDisks etc.
return true
}
default: // for cases like drivers etc
return true
}
}

View File

@@ -0,0 +1,206 @@
package test
import (
"bufio"
"fmt"
"os"
"strings"
)
// DEPRECATED_GROUPS stores API groups that are deprecated and therefore are not supported in further developement
var DEPRECATED_GROUPS = []string{
"/cloudapi/machine/",
"/cloudapi/cloudspace/",
"//cloudbroker/pgpu/",
"/cloudapi/gpu/",
"/cloudapi/portforwarding/",
"/cloudapi/account/create",
"/cloudapi/account/listVMs",
"/cloudapi/account/listCS",
"/cloudapi/account/getStats",
"/cloudapi/vgpu/list",
"/cloudapi/compute/affinityGroupCheckStart",
"/cloudapi/vins/staticRouteAccessGrant",
"/cloudapi/vins/staticRouteAccessRevoke",
"/cloudbroker/resource_optimizer",
"/cloudbroker/account/listVMs",
"/cloudbroker/account/listCS",
"/cloudbroker/machine/",
"/cloudbroker/bservice/",
"/cloudbroker/auditcollector/",
"/cloudbroker/health/",
"/cloudbroker/metering/",
"/cloudbroker/cloudspace/",
"/cloudbroker/vnfdev/",
"/cloudbroker/auditbeat/",
"/cloudbroker/zeroaccess/",
"/cloudbroker/qos/",
"/cloudbroker/backupcreator/",
"/cloudbroker/resmon/",
"/cloudbroker/pgpu/",
"/cloudbroker/job/",
"/cloudbroker/tlock/",
"/cloudbroker/eco/",
"/cloudbroker/iaas/",
"/cloudbroker/diagnostics/",
"/cloudbroker/milestones/",
"/cloudbroker/compute/affinityGroupCheckStart",
"/cloudbroker/vins/staticRouteAccessGrant",
"/cloudbroker/vins/staticRouteAccessRevoke",
"/restmachine/cloudbroker/image/updateNodes",
"/restmachine/cloudbroker/image/uploadImageFile",
}
// getUrlsFromBytes converts bytes to array of urls strings
func getUrlsFromBytes(bytes []byte) ([]string, error) {
paths, err := getMapFromFile(bytes)
if err != nil {
return nil, err
}
urls := make([]string, 0, len(paths))
for p := range paths {
if validateUrlFromJson(p) {
urls = append(urls, p)
}
}
return urls, nil
}
func validateUrlFromJson(url string) bool {
if !strings.HasPrefix(url, "/restmachine/cloudapi/") && !strings.HasPrefix(url, "/restmachine/cloudbroker/") && !strings.HasPrefix(url, "/restmachine/sdn/") {
return false
}
for _, deprecated := range DEPRECATED_GROUPS {
if strings.Contains(url, deprecated) {
return false
}
}
return true
}
// getMissingDecortUrls returns array of url strings that are present in json swagger docs and absent in decort-sdk handlers.
func getMissingDecortUrls(jsonUrls, decortUrls []string) []string {
result := make([]string, 0, len(jsonUrls))
for _, jsonUrl := range jsonUrls {
var found bool
for _, decortUrl := range decortUrls {
if jsonUrl == decortUrl {
found = true
break
}
}
if !found {
result = append(result, jsonUrl)
}
}
return result
}
// getMissingDecortUrls returns array of url strings that are present in json swagger docs and absent in decort-sdk handlers.
func getDeprecatedDecortUrls(jsonUrls, decortUrls []string) []string {
result := make([]string, 0, len(decortUrls))
for _, decortUrl := range decortUrls {
var found bool
for _, jsonUrl := range jsonUrls {
if jsonUrl == decortUrl {
found = true
break
}
}
if !found && validateUrlFromJson(decortUrl) {
result = append(result, decortUrl)
}
}
return result
}
// readUrlFromDir reads all url addresses from given directory and its subdirectories and subfiles and returns array of urls found. Capacity will
func readUrlFromDir(dirName string, capacity int) []string {
result := make([]string, 0, capacity)
items, _ := os.ReadDir(dirName)
for _, item := range items {
itemName := dirName + "/" + item.Name()
if item.IsDir() {
subitems, _ := os.ReadDir(itemName)
for _, subitem := range subitems {
subName := itemName + "/" + subitem.Name()
files, _ := os.ReadDir(subName)
if item.IsDir() {
for _, file := range files {
fileName := dirName + "/" + item.Name() + "/" + subitem.Name() + "/" + file.Name()
if !file.IsDir() {
url, err := readUrlFromFile(fileName)
if err != nil {
continue
}
result = append(result, url...)
}
}
}
if !subitem.IsDir() {
url, err := readUrlFromFile(subName)
if err != nil {
continue
}
result = append(result, url...)
}
}
} else {
url, err := readUrlFromFile(itemName)
if err != nil {
continue
}
result = append(result, url...)
}
}
return result
}
// readUrlFromFile reads url address of format url := "/cloudapi/vins/audits" or url := "/cloudbroker/vins/audits" from file with name fileName.
func readUrlFromFile(fileName string) ([]string, error) {
var result []string
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
if strings.Contains(scanner.Text(), `"/cloudapi/`) {
indexStart := strings.Index(scanner.Text(), "/cloudapi/")
url := strings.Trim(scanner.Text()[indexStart:], `"`)
result = append(result, "/restmachine"+url)
}
if strings.Contains(scanner.Text(), `"/cloudbroker/`) {
indexStart := strings.Index(scanner.Text(), "/cloudbroker/")
url := strings.Trim(scanner.Text()[indexStart:], `"`)
result = append(result, "/restmachine"+url)
}
if strings.Contains(scanner.Text(), `"/sdn/`) {
indexStart := strings.Index(scanner.Text(), "/sdn/")
url := strings.Trim(scanner.Text()[indexStart:], `"`)
result = append(result, "/restmachine"+url)
}
}
if len(result) == 0 {
return nil, fmt.Errorf("file %s doesn't contain any url", fileName)
}
return result, nil
}