test git
This commit is contained in:
@@ -1,5 +0,0 @@
|
||||
# delta
|
||||
AppID="app_id"
|
||||
AppSecret="app_secret"
|
||||
SSOURL="https://sso-delta.qa.loc:8443"
|
||||
DecortURL="https://delta.qa.loc/"
|
||||
@@ -1,151 +0,0 @@
|
||||
# Авто тесты при переходе на новую версию платформы
|
||||
|
||||
## Содержание
|
||||
|
||||
- [Авто тесты при переходе на новую версию платформы](#авто-тесты-при-переходе-на-новую-версию-платформы)
|
||||
- [Содержание](#содержание)
|
||||
- [Подготовка к тестам](#подготовка-к-тестам)
|
||||
- [Тесты 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
@@ -1,7 +0,0 @@
|
||||
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
@@ -1,292 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,435 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user