mirror of https://github.com/kubevela/kubevela.git
Compare commits
6 Commits
99d23e60a0
...
fe9631b038
| Author | SHA1 | Date |
|---|---|---|
|
|
fe9631b038 | |
|
|
305a90f428 | |
|
|
d1f077ee0d | |
|
|
260fc1a294 | |
|
|
24f6718619 | |
|
|
44ac92d1ba |
|
|
@ -24,14 +24,43 @@ import (
|
|||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
const registryConfigMapName = "vela-addon-registry"
|
||||
const registriesKey = "registries"
|
||||
const tokenSecretNamePrefix = "addon-registry-"
|
||||
|
||||
// TokenSource is an interface for addon source that has token
|
||||
type TokenSource interface {
|
||||
// GetToken return the token of the source
|
||||
GetToken() string
|
||||
// SetToken set the token of the source
|
||||
SetToken(string)
|
||||
// SetTokenSecretRef set the token secret ref to the source
|
||||
SetTokenSecretRef(string)
|
||||
// GetTokenSecretRef return the token secret ref of the source
|
||||
GetTokenSecretRef() string
|
||||
}
|
||||
|
||||
// GetTokenSource return the token source of the registry
|
||||
func (r *Registry) GetTokenSource() TokenSource {
|
||||
if r.Git != nil {
|
||||
return r.Git
|
||||
}
|
||||
if r.Gitee != nil {
|
||||
return r.Gitee
|
||||
}
|
||||
if r.Gitlab != nil {
|
||||
return r.Gitlab
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Registry represent a addon registry model
|
||||
type Registry struct {
|
||||
|
|
@ -62,28 +91,49 @@ type registryImpl struct {
|
|||
client client.Client
|
||||
}
|
||||
|
||||
func (r registryImpl) ListRegistries(ctx context.Context) ([]Registry, error) {
|
||||
// getRegistries is a helper to fetch and unmarshal all registries from the ConfigMap
|
||||
func (r registryImpl) getRegistries(ctx context.Context) (map[string]Registry, *v1.ConfigMap, error) {
|
||||
cm := &v1.ConfigMap{}
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm); err != nil {
|
||||
return nil, err
|
||||
err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if _, ok := cm.Data[registriesKey]; !ok {
|
||||
return nil, NewAddonError("Error addon registry configmap registry-key not exist")
|
||||
return nil, nil, NewAddonError("error addon registry configmap registry-key not exist")
|
||||
}
|
||||
registries := map[string]Registry{}
|
||||
if err := json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries); err != nil {
|
||||
return nil, cm, err
|
||||
}
|
||||
return registries, cm, nil
|
||||
}
|
||||
|
||||
func (r registryImpl) ListRegistries(ctx context.Context) ([]Registry, error) {
|
||||
registries, _, err := r.getRegistries(ctx)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return []Registry{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res []Registry
|
||||
for _, registry := range registries {
|
||||
if err := loadTokenFromSecret(ctx, r.client, ®istry); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, registry)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (r registryImpl) AddRegistry(ctx context.Context, registry Registry) error {
|
||||
cm := &v1.ConfigMap{}
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm); err != nil {
|
||||
if err := createOrUpdateTokenSecret(ctx, r.client, ®istry); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registries, cm, err := r.getRegistries(ctx)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
b, err := json.Marshal(map[string]Registry{
|
||||
registry.Name: registry,
|
||||
|
|
@ -91,7 +141,7 @@ func (r registryImpl) AddRegistry(ctx context.Context, registry Registry) error
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm = &v1.ConfigMap{
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: registryConfigMapName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
|
|
@ -104,48 +154,110 @@ func (r registryImpl) AddRegistry(ctx context.Context, registry Registry) error
|
|||
}
|
||||
return err
|
||||
}
|
||||
registries := map[string]Registry{}
|
||||
if err := json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registries[registry.Name] = registry
|
||||
b, err := json.Marshal(registries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{
|
||||
registriesKey: string(b),
|
||||
}
|
||||
cm.Data[registriesKey] = string(b)
|
||||
return r.client.Update(ctx, cm)
|
||||
}
|
||||
|
||||
// createOrUpdateTokenSecret will create or update a secret to store registry token
|
||||
func createOrUpdateTokenSecret(ctx context.Context, cli client.Client, registry *Registry) error {
|
||||
source := registry.GetTokenSource()
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
token := source.GetToken()
|
||||
if token == "" {
|
||||
return nil
|
||||
}
|
||||
return migrateInlineTokenToSecret(ctx, cli, registry, source, token)
|
||||
}
|
||||
|
||||
// migrateInlineTokenToSecret will migrate an inline token to a secret.
|
||||
// It will take the token from the registry object, create/update a secret, and set the secret ref on the registry object.
|
||||
func migrateInlineTokenToSecret(ctx context.Context, cli client.Client, registry *Registry, source TokenSource, token string) error {
|
||||
log := logf.FromContext(ctx)
|
||||
secretName := tokenSecretNamePrefix + registry.Name
|
||||
source.SetTokenSecretRef(secretName)
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Type: v1.SecretTypeOpaque,
|
||||
Data: map[string][]byte{
|
||||
"token": []byte(token),
|
||||
},
|
||||
}
|
||||
|
||||
err := cli.Create(ctx, secret)
|
||||
if err != nil {
|
||||
if apierrors.IsAlreadyExists(err) {
|
||||
existingSecret := &v1.Secret{}
|
||||
if err := cli.Get(ctx, types.NamespacedName{Name: secretName, Namespace: velatypes.DefaultKubeVelaNS}, existingSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
existingSecret.Data = secret.Data
|
||||
if err := cli.Update(ctx, existingSecret); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Successfully updated secret for addon registry token", "registry", registry.Name, "secret", secretName)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
log.Info("Successfully created secret for addon registry token", "registry", registry.Name, "secret", secretName)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r registryImpl) DeleteRegistry(ctx context.Context, name string) error {
|
||||
cm := &v1.ConfigMap{}
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm); err != nil {
|
||||
registries, cm, err := r.getRegistries(ctx)
|
||||
if err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
registries := map[string]Registry{}
|
||||
if err := json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries); err != nil {
|
||||
return err
|
||||
|
||||
reg, ok := registries[name]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if source := reg.GetTokenSource(); source != nil {
|
||||
if secretName := source.GetTokenSecretRef(); secretName != "" {
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
}
|
||||
if err := r.client.Delete(ctx, secret); err != nil && !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delete(registries, name)
|
||||
b, err := json.Marshal(registries)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{
|
||||
registriesKey: string(b),
|
||||
}
|
||||
cm.Data[registriesKey] = string(b)
|
||||
return r.client.Update(ctx, cm)
|
||||
}
|
||||
|
||||
func (r registryImpl) UpdateRegistry(ctx context.Context, registry Registry) error {
|
||||
cm := &v1.ConfigMap{}
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm); err != nil {
|
||||
if err := createOrUpdateTokenSecret(ctx, r.client, ®istry); err != nil {
|
||||
return err
|
||||
}
|
||||
registries := map[string]Registry{}
|
||||
if err := json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries); err != nil {
|
||||
registries, cm, err := r.getRegistries(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, ok := registries[registry.Name]; !ok {
|
||||
|
|
@ -156,25 +268,50 @@ func (r registryImpl) UpdateRegistry(ctx context.Context, registry Registry) err
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cm.Data = map[string]string{
|
||||
registriesKey: string(b),
|
||||
}
|
||||
cm.Data[registriesKey] = string(b)
|
||||
return r.client.Update(ctx, cm)
|
||||
}
|
||||
|
||||
func (r registryImpl) GetRegistry(ctx context.Context, name string) (Registry, error) {
|
||||
var res Registry
|
||||
cm := &v1.ConfigMap{}
|
||||
if err := r.client.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: registryConfigMapName}, cm); err != nil {
|
||||
return res, err
|
||||
registries, _, err := r.getRegistries(ctx)
|
||||
if err != nil {
|
||||
return Registry{}, err
|
||||
}
|
||||
registries := map[string]Registry{}
|
||||
if err := json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries); err != nil {
|
||||
return res, err
|
||||
res, ok := registries[name]
|
||||
if !ok {
|
||||
return res, apierrors.NewNotFound(schema.GroupResource{Group: "addons.kubevela.io", Resource: "Registry"}, name)
|
||||
}
|
||||
var notExist bool
|
||||
if res, notExist = registries[name]; !notExist {
|
||||
return res, fmt.Errorf("registry name %s not found", name)
|
||||
if err := loadTokenFromSecret(ctx, r.client, &res); err != nil {
|
||||
return res, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// loadTokenFromSecret will load token from secret if exists
|
||||
// and set it to the source of the registry object
|
||||
func loadTokenFromSecret(ctx context.Context, cli client.Client, registry *Registry) error {
|
||||
source := registry.GetTokenSource()
|
||||
if source == nil {
|
||||
return nil
|
||||
}
|
||||
secretName := source.GetTokenSecretRef()
|
||||
if secretName == "" {
|
||||
if source.GetToken() != "" {
|
||||
// For backward compatibility, token can be stored in configmap directly.
|
||||
// This is not secure, so we print a warning and recommend user to upgrade.
|
||||
// The upgrade can be done by editing and saving the addon registry again.
|
||||
fmt.Printf("Warning: addon registry %s is using an insecure token stored in ConfigMap. Please edit and save this addon registry again to migrate the token to a secret.\n", registry.Name)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
secret := &v1.Secret{}
|
||||
if err := cli.Get(ctx, types.NamespacedName{Namespace: velatypes.DefaultKubeVelaNS, Name: secretName}, secret); err != nil {
|
||||
if apierrors.IsNotFound(err) {
|
||||
// If the secret is not found, we consider the token is empty
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
source.SetToken(string(secret.Data["token"]))
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,643 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package addon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
velatypes "github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
func TestAddonRegistry(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
Token: "test-token",
|
||||
},
|
||||
}
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
t.Run("add registry", func(t *testing.T) {
|
||||
err := ds.AddRegistry(ctx, testRegistry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var cm v1.ConfigMap
|
||||
err = client.Get(ctx, types.NamespacedName{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS}, &cm)
|
||||
assert.NoError(t, err)
|
||||
var registries map[string]Registry
|
||||
err = json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(registries))
|
||||
gotRegistry := registries["test-registry"]
|
||||
assert.Equal(t, "", gotRegistry.Git.Token)
|
||||
assert.Equal(t, "addon-registry-test-registry", gotRegistry.Git.TokenSecretRef)
|
||||
|
||||
var secret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token", string(secret.Data["token"]))
|
||||
})
|
||||
|
||||
t.Run("update registry", func(t *testing.T) {
|
||||
updatedRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo-updated",
|
||||
Token: "test-token-updated",
|
||||
},
|
||||
}
|
||||
err := ds.UpdateRegistry(ctx, updatedRegistry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var secret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token-updated", string(secret.Data["token"]))
|
||||
})
|
||||
|
||||
t.Run("list and get registry", func(t *testing.T) {
|
||||
registries, err := ds.ListRegistries(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(registries))
|
||||
assert.Equal(t, "test-token-updated", registries[0].Git.Token)
|
||||
|
||||
reg, err := ds.GetRegistry(ctx, "test-registry")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token-updated", reg.Git.Token)
|
||||
})
|
||||
|
||||
t.Run("delete registry", func(t *testing.T) {
|
||||
err := ds.DeleteRegistry(ctx, "test-registry")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var cm v1.ConfigMap
|
||||
err = client.Get(ctx, types.NamespacedName{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS}, &cm)
|
||||
assert.NoError(t, err)
|
||||
var registries map[string]Registry
|
||||
err = json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(registries))
|
||||
|
||||
var secret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTokenSource(t *testing.T) {
|
||||
gitSource := &GitAddonSource{URL: "https://github.com/kubevela/catalog.git"}
|
||||
giteeSource := &GiteeAddonSource{URL: "https://gitee.com/kubevela/catalog.git"}
|
||||
gitlabSource := &GitlabAddonSource{URL: "https://gitlab.com/kubevela/catalog.git"}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
registry *Registry
|
||||
expectedSource TokenSource
|
||||
}{
|
||||
{
|
||||
name: "git source",
|
||||
registry: &Registry{
|
||||
Git: gitSource,
|
||||
},
|
||||
expectedSource: gitSource,
|
||||
},
|
||||
{
|
||||
name: "gitee source",
|
||||
registry: &Registry{
|
||||
Gitee: giteeSource,
|
||||
},
|
||||
expectedSource: giteeSource,
|
||||
},
|
||||
{
|
||||
name: "gitlab source",
|
||||
registry: &Registry{
|
||||
Gitlab: gitlabSource,
|
||||
},
|
||||
expectedSource: gitlabSource,
|
||||
},
|
||||
{
|
||||
name: "git and gitee source",
|
||||
registry: &Registry{
|
||||
Git: gitSource,
|
||||
Gitee: giteeSource,
|
||||
},
|
||||
expectedSource: gitSource,
|
||||
},
|
||||
{
|
||||
name: "gitee and gitlab source",
|
||||
registry: &Registry{
|
||||
Gitee: giteeSource,
|
||||
Gitlab: gitlabSource,
|
||||
},
|
||||
expectedSource: giteeSource,
|
||||
},
|
||||
{
|
||||
name: "all token sources",
|
||||
registry: &Registry{
|
||||
Git: gitSource,
|
||||
Gitee: giteeSource,
|
||||
Gitlab: gitlabSource,
|
||||
},
|
||||
expectedSource: gitSource,
|
||||
},
|
||||
{
|
||||
name: "no token source",
|
||||
registry: &Registry{
|
||||
Helm: &HelmSource{},
|
||||
},
|
||||
expectedSource: nil,
|
||||
},
|
||||
{
|
||||
name: "empty registry",
|
||||
registry: &Registry{},
|
||||
expectedSource: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
source := tc.registry.GetTokenSource()
|
||||
assert.Equal(t, tc.expectedSource, source)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddRegistry(t *testing.T) {
|
||||
t.Run("Test adding a registry", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
testRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
Token: "test-token",
|
||||
},
|
||||
}
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
err := ds.AddRegistry(ctx, testRegistry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var cm v1.ConfigMap
|
||||
err = client.Get(ctx, types.NamespacedName{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS}, &cm)
|
||||
assert.NoError(t, err)
|
||||
var registries map[string]Registry
|
||||
err = json.Unmarshal([]byte(cm.Data[registriesKey]), ®istries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(registries))
|
||||
gotRegistry := registries["test-registry"]
|
||||
assert.Equal(t, "", gotRegistry.Git.Token)
|
||||
assert.Equal(t, "addon-registry-test-registry", gotRegistry.Git.TokenSecretRef)
|
||||
|
||||
var secret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token", string(secret.Data["token"]))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUpdateRegistry(t *testing.T) {
|
||||
t.Run("Test updating a registry", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
updatedRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo-updated",
|
||||
Token: "test-token-updated",
|
||||
},
|
||||
}
|
||||
// Pre-existing state
|
||||
existingRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
TokenSecretRef: "addon-registry-test-registry",
|
||||
},
|
||||
}
|
||||
registries := map[string]Registry{"test-registry": existingRegistry}
|
||||
registriesBytes, err := json.Marshal(registries)
|
||||
assert.NoError(t, err)
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: registryConfigMapName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string]string{
|
||||
registriesKey: string(registriesBytes),
|
||||
},
|
||||
}
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "addon-registry-test-registry",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm, secret).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
err = ds.UpdateRegistry(ctx, updatedRegistry)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var updatedSecret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &updatedSecret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token-updated", string(updatedSecret.Data["token"]))
|
||||
|
||||
var updatedCm v1.ConfigMap
|
||||
err = client.Get(ctx, types.NamespacedName{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS}, &updatedCm)
|
||||
assert.NoError(t, err)
|
||||
var updatedRegistries map[string]Registry
|
||||
err = json.Unmarshal([]byte(updatedCm.Data[registriesKey]), &updatedRegistries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "http://github.com/test/repo-updated", updatedRegistries["test-registry"].Git.URL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestListRegistry(t *testing.T) {
|
||||
t.Run("Test listing registries", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// Pre-existing state
|
||||
existingRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
TokenSecretRef: "addon-registry-test-registry",
|
||||
},
|
||||
}
|
||||
registries := map[string]Registry{"test-registry": existingRegistry}
|
||||
registriesBytes, err := json.Marshal(registries)
|
||||
assert.NoError(t, err)
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: registryConfigMapName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string]string{
|
||||
registriesKey: string(registriesBytes),
|
||||
},
|
||||
}
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "addon-registry-test-registry",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm, secret).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
// Test List
|
||||
listedRegistries, err := ds.ListRegistries(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 1, len(listedRegistries))
|
||||
assert.Equal(t, "test-token", listedRegistries[0].Git.Token)
|
||||
assert.Equal(t, "http://github.com/test/repo", listedRegistries[0].Git.URL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRegistry(t *testing.T) {
|
||||
t.Run("Test getting a single registry", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// Pre-existing state
|
||||
existingRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
TokenSecretRef: "addon-registry-test-registry",
|
||||
},
|
||||
}
|
||||
registries := map[string]Registry{"test-registry": existingRegistry}
|
||||
registriesBytes, err := json.Marshal(registries)
|
||||
assert.NoError(t, err)
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: registryConfigMapName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string]string{
|
||||
registriesKey: string(registriesBytes),
|
||||
},
|
||||
}
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "addon-registry-test-registry",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm, secret).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
// Test Get
|
||||
reg, err := ds.GetRegistry(ctx, "test-registry")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-token", reg.Git.Token)
|
||||
assert.Equal(t, "http://github.com/test/repo", reg.Git.URL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestDeleteRegistry(t *testing.T) {
|
||||
t.Run("Test deleting a registry", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
// Pre-existing state
|
||||
existingRegistry := Registry{
|
||||
Name: "test-registry",
|
||||
Git: &GitAddonSource{
|
||||
URL: "http://github.com/test/repo",
|
||||
TokenSecretRef: "addon-registry-test-registry",
|
||||
},
|
||||
}
|
||||
registries := map[string]Registry{"test-registry": existingRegistry}
|
||||
registriesBytes, err := json.Marshal(registries)
|
||||
assert.NoError(t, err)
|
||||
cm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: registryConfigMapName,
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string]string{
|
||||
registriesKey: string(registriesBytes),
|
||||
},
|
||||
}
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "addon-registry-test-registry",
|
||||
Namespace: velatypes.DefaultKubeVelaNS,
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"token": []byte("test-token"),
|
||||
},
|
||||
}
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
client := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm, secret).Build()
|
||||
ds := NewRegistryDataStore(client)
|
||||
|
||||
err = ds.DeleteRegistry(ctx, "test-registry")
|
||||
assert.NoError(t, err)
|
||||
|
||||
var updatedCm v1.ConfigMap
|
||||
err = client.Get(ctx, types.NamespacedName{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS}, &updatedCm)
|
||||
assert.NoError(t, err)
|
||||
var updatedRegistries map[string]Registry
|
||||
err = json.Unmarshal([]byte(updatedCm.Data[registriesKey]), &updatedRegistries)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(updatedRegistries))
|
||||
|
||||
var deletedSecret v1.Secret
|
||||
err = client.Get(ctx, types.NamespacedName{Name: "addon-registry-test-registry", Namespace: velatypes.DefaultKubeVelaNS}, &deletedSecret)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetRegistries(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
|
||||
// valid configmap with one registry
|
||||
validRegistries := map[string]Registry{"test-registry": {Name: "test-registry"}}
|
||||
validRegistriesBytes, err := json.Marshal(validRegistries)
|
||||
assert.NoError(t, err)
|
||||
validCm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS},
|
||||
Data: map[string]string{registriesKey: string(validRegistriesBytes)},
|
||||
}
|
||||
|
||||
// configmap with invalid json
|
||||
invalidJSONCm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS},
|
||||
Data: map[string]string{registriesKey: "invalid-json"},
|
||||
}
|
||||
|
||||
// configmap with missing key
|
||||
missingKeyCm := &v1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: registryConfigMapName, Namespace: velatypes.DefaultKubeVelaNS},
|
||||
Data: map[string]string{"another-key": "some-data"},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
client client.Client
|
||||
expectErr bool
|
||||
expectRegNum int
|
||||
}{
|
||||
"success": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(validCm).Build(),
|
||||
expectErr: false,
|
||||
expectRegNum: 1,
|
||||
},
|
||||
"configmap not found": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
expectErr: true,
|
||||
},
|
||||
"invalid json": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(invalidJSONCm).Build(),
|
||||
expectErr: true,
|
||||
},
|
||||
"registries key missing": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(missingKeyCm).Build(),
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
ds := registryImpl{client: tc.client}
|
||||
registries, _, err := ds.getRegistries(ctx)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectRegNum, len(registries))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadTokenFromSecret(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
|
||||
secret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "addon-registry-test", Namespace: velatypes.DefaultKubeVelaNS},
|
||||
Data: map[string][]byte{"token": []byte("test-token")},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
client client.Client
|
||||
registry *Registry
|
||||
expectErr bool
|
||||
expectToken string
|
||||
}{
|
||||
"success": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(secret).Build(),
|
||||
registry: &Registry{
|
||||
Name: "test",
|
||||
Git: &GitAddonSource{URL: "http://github.com/test/repo", TokenSecretRef: "addon-registry-test"},
|
||||
},
|
||||
expectErr: false,
|
||||
expectToken: "test-token",
|
||||
},
|
||||
"secret not found": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{
|
||||
Name: "test",
|
||||
Git: &GitAddonSource{URL: "http://github.com/test/repo", TokenSecretRef: "addon-registry-test"},
|
||||
},
|
||||
expectErr: false,
|
||||
expectToken: "",
|
||||
},
|
||||
"no token source": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{Name: "test"},
|
||||
expectErr: false,
|
||||
expectToken: "",
|
||||
},
|
||||
"no secret ref": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{Name: "test", Git: &GitAddonSource{URL: "http://github.com/test/repo"}},
|
||||
expectErr: false,
|
||||
expectToken: "",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := loadTokenFromSecret(ctx, tc.client, tc.registry)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tc.registry.Git != nil {
|
||||
assert.Equal(t, tc.expectToken, tc.registry.Git.Token)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrUpdateTokenSecret(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1.AddToScheme(scheme))
|
||||
|
||||
existingSecret := &v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "addon-registry-test", Namespace: velatypes.DefaultKubeVelaNS},
|
||||
Data: map[string][]byte{"token": []byte("old-token")},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
client client.Client
|
||||
registry *Registry
|
||||
expectErr bool
|
||||
expectToken string
|
||||
expectSecret bool
|
||||
}{
|
||||
"create new secret": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{
|
||||
Name: "test",
|
||||
Git: &GitAddonSource{Token: "new-token"},
|
||||
},
|
||||
expectErr: false,
|
||||
expectToken: "new-token",
|
||||
expectSecret: true,
|
||||
},
|
||||
"update existing secret": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(existingSecret).Build(),
|
||||
registry: &Registry{
|
||||
Name: "test",
|
||||
Git: &GitAddonSource{Token: "updated-token"},
|
||||
},
|
||||
expectErr: false,
|
||||
expectToken: "updated-token",
|
||||
expectSecret: true,
|
||||
},
|
||||
"no token source": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{Name: "test"},
|
||||
expectErr: false,
|
||||
expectSecret: false,
|
||||
},
|
||||
"empty token": {
|
||||
client: fake.NewClientBuilder().WithScheme(scheme).Build(),
|
||||
registry: &Registry{Name: "test", Git: &GitAddonSource{Token: ""}},
|
||||
expectErr: false,
|
||||
expectSecret: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := createOrUpdateTokenSecret(ctx, tc.client, tc.registry)
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
if tc.expectSecret {
|
||||
var secret v1.Secret
|
||||
err := tc.client.Get(ctx, types.NamespacedName{Name: "addon-registry-test", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectToken, string(secret.Data["token"]))
|
||||
assert.Equal(t, "addon-registry-test", tc.registry.GetTokenSource().GetTokenSecretRef())
|
||||
} else {
|
||||
var secret v1.Secret
|
||||
err := tc.client.Get(ctx, types.NamespacedName{Name: "addon-registry-test", Namespace: velatypes.DefaultKubeVelaNS}, &secret)
|
||||
assert.True(t, apierrors.IsNotFound(err))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -56,37 +56,32 @@ type Source interface {
|
|||
|
||||
// GitAddonSource defines the information about the Git as addon source
|
||||
type GitAddonSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TokenSecretRef string `json:"tokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// GiteeAddonSource defines the information about the Gitee as addon source
|
||||
type GiteeAddonSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
// GetToken returns the token of the source
|
||||
func (g *GitAddonSource) GetToken() string {
|
||||
return g.Token
|
||||
}
|
||||
|
||||
// GitlabAddonSource defines the information about the Gitlab as addon source
|
||||
type GitlabAddonSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Repo string `json:"repo,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
// SetToken set the token of the source
|
||||
func (g *GitAddonSource) SetToken(token string) {
|
||||
g.Token = token
|
||||
g.TokenSecretRef = ""
|
||||
}
|
||||
|
||||
// HelmSource defines the information about the helm repo addon source
|
||||
type HelmSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
InsecureSkipTLS bool `json:"insecureSkipTLS,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
// SetTokenSecretRef set the token secret ref to the source
|
||||
func (g *GitAddonSource) SetTokenSecretRef(secretName string) {
|
||||
g.Token = ""
|
||||
g.TokenSecretRef = secretName
|
||||
}
|
||||
|
||||
// SafeCopier is an interface to copy Struct without sensitive fields, such as Token, Username, Password
|
||||
type SafeCopier interface {
|
||||
SafeCopy() interface{}
|
||||
// GetTokenSecretRef return the token secret ref of the source
|
||||
func (g *GitAddonSource) GetTokenSecretRef() string {
|
||||
return g.TokenSecretRef
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
|
|
@ -95,22 +90,85 @@ func (g *GitAddonSource) SafeCopy() *GitAddonSource {
|
|||
return nil
|
||||
}
|
||||
return &GitAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
TokenSecretRef: g.TokenSecretRef,
|
||||
}
|
||||
}
|
||||
|
||||
// GiteeAddonSource defines the information about the Gitee as addon source
|
||||
type GiteeAddonSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TokenSecretRef string `json:"tokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// GetToken return the token of the source
|
||||
func (g *GiteeAddonSource) GetToken() string {
|
||||
return g.Token
|
||||
}
|
||||
|
||||
// SetToken set the token of the source
|
||||
func (g *GiteeAddonSource) SetToken(token string) {
|
||||
g.Token = token
|
||||
g.TokenSecretRef = ""
|
||||
}
|
||||
|
||||
// SetTokenSecretRef set the token secret ref to the source
|
||||
func (g *GiteeAddonSource) SetTokenSecretRef(secretName string) {
|
||||
g.Token = ""
|
||||
g.TokenSecretRef = secretName
|
||||
}
|
||||
|
||||
// GetTokenSecretRef return the token secret ref of the source
|
||||
func (g *GiteeAddonSource) GetTokenSecretRef() string {
|
||||
return g.TokenSecretRef
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GiteeAddonSource) SafeCopy() *GiteeAddonSource {
|
||||
if g == nil {
|
||||
return nil
|
||||
}
|
||||
return &GiteeAddonSource{
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
URL: g.URL,
|
||||
Path: g.Path,
|
||||
TokenSecretRef: g.TokenSecretRef,
|
||||
}
|
||||
}
|
||||
|
||||
// GitlabAddonSource defines the information about Gitlab as an addon source
|
||||
type GitlabAddonSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
Repo string `json:"repo,omitempty" validate:"required"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Token string `json:"token,omitempty"`
|
||||
TokenSecretRef string `json:"tokenSecretRef,omitempty"`
|
||||
}
|
||||
|
||||
// GetToken return the token of the source
|
||||
func (g *GitlabAddonSource) GetToken() string {
|
||||
return g.Token
|
||||
}
|
||||
|
||||
// SetToken set the token of the source
|
||||
func (g *GitlabAddonSource) SetToken(token string) {
|
||||
g.Token = token
|
||||
g.TokenSecretRef = ""
|
||||
}
|
||||
|
||||
// SetTokenSecretRef set the token secret ref to the source
|
||||
func (g *GitlabAddonSource) SetTokenSecretRef(secretName string) {
|
||||
g.Token = ""
|
||||
g.TokenSecretRef = secretName
|
||||
}
|
||||
|
||||
// GetTokenSecretRef return the token secret ref of the source
|
||||
func (g *GitlabAddonSource) GetTokenSecretRef() string {
|
||||
return g.TokenSecretRef
|
||||
}
|
||||
|
||||
// SafeCopy hides field Token
|
||||
func (g *GitlabAddonSource) SafeCopy() *GitlabAddonSource {
|
||||
if g == nil {
|
||||
|
|
@ -123,6 +181,19 @@ func (g *GitlabAddonSource) SafeCopy() *GitlabAddonSource {
|
|||
}
|
||||
}
|
||||
|
||||
// HelmSource defines the information about the helm repo addon source
|
||||
type HelmSource struct {
|
||||
URL string `json:"url,omitempty" validate:"required"`
|
||||
InsecureSkipTLS bool `json:"insecureSkipTLS,omitempty"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// SafeCopier is an interface to copy struct without sensitive fields, such as Token, Username, Password
|
||||
type SafeCopier interface {
|
||||
SafeCopy() interface{}
|
||||
}
|
||||
|
||||
// SafeCopy hides field Username, Password
|
||||
func (h *HelmSource) SafeCopy() *HelmSource {
|
||||
if h == nil {
|
||||
|
|
|
|||
|
|
@ -275,3 +275,47 @@ func TestSafeCopy(t *testing.T) {
|
|||
assert.Empty(t, shelm.Password)
|
||||
assert.Equal(t, "https://hub.vela.com/chartrepo/addons", shelm.URL)
|
||||
}
|
||||
|
||||
func TestTokenSource(t *testing.T) {
|
||||
t.Run("GitAddonSource", func(t *testing.T) {
|
||||
source := &GitAddonSource{}
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetToken("test-token")
|
||||
assert.Equal(t, "test-token", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetTokenSecretRef("test-secret")
|
||||
assert.Equal(t, "test-secret", source.GetTokenSecretRef())
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
})
|
||||
|
||||
t.Run("GiteeAddonSource", func(t *testing.T) {
|
||||
source := &GiteeAddonSource{}
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetToken("test-token")
|
||||
assert.Equal(t, "test-token", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetTokenSecretRef("test-secret")
|
||||
assert.Equal(t, "test-secret", source.GetTokenSecretRef())
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
})
|
||||
|
||||
t.Run("GitlabAddonSource", func(t *testing.T) {
|
||||
source := &GitlabAddonSource{}
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetToken("test-token")
|
||||
assert.Equal(t, "test-token", source.GetToken())
|
||||
assert.Equal(t, "", source.GetTokenSecretRef())
|
||||
|
||||
source.SetTokenSecretRef("test-secret")
|
||||
assert.Equal(t, "test-secret", source.GetTokenSecretRef())
|
||||
assert.Equal(t, "", source.GetToken())
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -465,7 +465,12 @@ func checkConflictDefs(ctx context.Context, k8sClient client.Client, defs []*uns
|
|||
}
|
||||
if owner.Name != appName {
|
||||
// if addon not belong to an addon or addon name is another one, we should put them in result
|
||||
res[checkDef.GetName()] = fmt.Sprintf("definition: %s in this addon already exist in %s \n", checkDef.GetName(), addon.AppName2Addon(appName))
|
||||
addonName := addon.AppName2Addon(owner.Name)
|
||||
// If owner.Name isn't an addon app name, show the owner's name directly as the addon name
|
||||
if addonName == "" {
|
||||
addonName = owner.Name
|
||||
}
|
||||
res[checkDef.GetName()] = fmt.Sprintf("definition: %s in this addon already exist in %s \n", checkDef.GetName(), addonName)
|
||||
}
|
||||
}
|
||||
if err != nil && !errors2.IsNotFound(err) {
|
||||
|
|
|
|||
|
|
@ -117,6 +117,86 @@ var _ = Describe("Test definition check", func() {
|
|||
Expect(err).Should(BeNil())
|
||||
Expect(len(usedApps)).Should(BeEquivalentTo(4))
|
||||
})
|
||||
|
||||
It("Test checkConflictDefs", func() {
|
||||
const appName = "addon-fluxcd"
|
||||
const otherAppName = "addon-other"
|
||||
isController := true
|
||||
|
||||
// A definition that doesn't exist in the cluster
|
||||
nonExistingDef := &unstructured.Unstructured{}
|
||||
nonExistingDef.SetAPIVersion("core.oam.dev/v1beta1")
|
||||
nonExistingDef.SetKind("ComponentDefinition")
|
||||
nonExistingDef.SetName("non-existing-def")
|
||||
nonExistingDef.SetNamespace(velatypes.DefaultKubeVelaNS)
|
||||
|
||||
// A definition that exists but has no owner
|
||||
defWithNoOwner := &unstructured.Unstructured{}
|
||||
defWithNoOwner.SetAPIVersion("core.oam.dev/v1beta1")
|
||||
defWithNoOwner.SetKind("ComponentDefinition")
|
||||
defWithNoOwner.SetName("def-no-owner")
|
||||
defWithNoOwner.SetNamespace(velatypes.DefaultKubeVelaNS)
|
||||
Expect(k8sClient.Create(ctx, defWithNoOwner)).Should(Succeed())
|
||||
defer func() {
|
||||
Expect(k8sClient.Delete(ctx, defWithNoOwner)).Should(Succeed())
|
||||
}()
|
||||
|
||||
// A definition that is owned by another addon
|
||||
defWithOtherOwner := &unstructured.Unstructured{}
|
||||
defWithOtherOwner.SetAPIVersion("core.oam.dev/v1beta1")
|
||||
defWithOtherOwner.SetKind("ComponentDefinition")
|
||||
defWithOtherOwner.SetName("def-other-owner")
|
||||
defWithOtherOwner.SetNamespace(velatypes.DefaultKubeVelaNS)
|
||||
defWithOtherOwner.SetOwnerReferences([]metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
Name: otherAppName,
|
||||
Controller: &isController,
|
||||
UID: "test-uid-other",
|
||||
},
|
||||
})
|
||||
Expect(k8sClient.Create(ctx, defWithOtherOwner)).Should(Succeed())
|
||||
defer func() {
|
||||
Expect(k8sClient.Delete(ctx, defWithOtherOwner)).Should(Succeed())
|
||||
}()
|
||||
|
||||
// A definition that is owned by the same addon
|
||||
defWithSameOwner := &unstructured.Unstructured{}
|
||||
defWithSameOwner.SetAPIVersion("core.oam.dev/v1beta1")
|
||||
defWithSameOwner.SetKind("ComponentDefinition")
|
||||
defWithSameOwner.SetName("def-same-owner")
|
||||
defWithSameOwner.SetNamespace(velatypes.DefaultKubeVelaNS)
|
||||
defWithSameOwner.SetOwnerReferences([]metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: v1beta1.SchemeGroupVersion.String(),
|
||||
Kind: v1beta1.ApplicationKind,
|
||||
Name: appName,
|
||||
Controller: &isController,
|
||||
UID: "test-uid-same",
|
||||
},
|
||||
})
|
||||
Expect(k8sClient.Create(ctx, defWithSameOwner)).Should(Succeed())
|
||||
defer func() {
|
||||
Expect(k8sClient.Delete(ctx, defWithSameOwner)).Should(Succeed())
|
||||
}()
|
||||
|
||||
By("Checking a mix of definitions for conflicts")
|
||||
defs := []*unstructured.Unstructured{
|
||||
nonExistingDef,
|
||||
defWithNoOwner,
|
||||
defWithOtherOwner,
|
||||
defWithSameOwner,
|
||||
}
|
||||
|
||||
conflicts, err := checkConflictDefs(ctx, k8sClient, defs, appName)
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(len(conflicts)).Should(Equal(2))
|
||||
Expect(conflicts).Should(HaveKey(defWithNoOwner.GetName()))
|
||||
Expect(conflicts[defWithNoOwner.GetName()]).Should(ContainSubstring("already exist and not belong to any addon"))
|
||||
Expect(conflicts).Should(HaveKey(defWithOtherOwner.GetName()))
|
||||
Expect(conflicts[defWithOtherOwner.GetName()]).Should(ContainSubstring("already exist in other"))
|
||||
})
|
||||
})
|
||||
|
||||
func TestMerge2Map(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,261 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package watcher
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
func TestApplicationMetricsWatcher(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
appRunning := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-running"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
},
|
||||
}
|
||||
appRendering := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-rendering"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRendering,
|
||||
},
|
||||
}
|
||||
appWithWorkflow := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-with-workflow"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step1",
|
||||
Type: "apply-component",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
appWithMixedWorkflow := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-with-mixed-workflow"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step1",
|
||||
Type: "apply-component",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded,
|
||||
},
|
||||
},
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step2",
|
||||
Type: "apply-component",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
StepStatus: workflowv1alpha1.StepStatus{
|
||||
Name: "step3",
|
||||
Type: "suspend",
|
||||
Phase: workflowv1alpha1.WorkflowStepPhaseRunning,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
app *v1beta1.Application
|
||||
op int
|
||||
wantPC map[string]int
|
||||
wantSC map[string]int
|
||||
wantPD map[string]struct{}
|
||||
wantSD map[string]struct{}
|
||||
}{
|
||||
"Add an application": {
|
||||
app: appRunning,
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Add an application with workflow": {
|
||||
app: appWithWorkflow,
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{"apply-component/succeeded#": 1},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{"apply-component/succeeded#": {}},
|
||||
},
|
||||
"Delete an application": {
|
||||
app: appRunning,
|
||||
op: -1,
|
||||
wantPC: map[string]int{"running": -1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Update an application": {
|
||||
app: appRendering,
|
||||
op: -1,
|
||||
wantPC: map[string]int{"rendering": -1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"rendering": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Nil app status": {
|
||||
app: &v1beta1.Application{},
|
||||
op: 1,
|
||||
wantPC: map[string]int{"-": 1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"-": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Add an application with mixed workflow": {
|
||||
app: appWithMixedWorkflow,
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{
|
||||
"apply-component/succeeded#": 1,
|
||||
"apply-component/failed#": 1,
|
||||
"suspend/running#": 1,
|
||||
},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{
|
||||
"apply-component/succeeded#": {},
|
||||
"apply-component/failed#": {},
|
||||
"suspend/running#": {},
|
||||
},
|
||||
},
|
||||
"Empty workflow steps": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-empty-workflow"},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{},
|
||||
},
|
||||
},
|
||||
},
|
||||
op: 1,
|
||||
wantPC: map[string]int{"running": 1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"running": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
"Unknown phase": {
|
||||
app: &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "app-unknown-phase"},
|
||||
Status: common.AppStatus{
|
||||
Phase: "unknown",
|
||||
},
|
||||
},
|
||||
op: 1,
|
||||
wantPC: map[string]int{"unknown": 1},
|
||||
wantSC: map[string]int{},
|
||||
wantPD: map[string]struct{}{"unknown": {}},
|
||||
wantSD: map[string]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
watcher := &applicationMetricsWatcher{
|
||||
phaseCounter: map[string]int{},
|
||||
stepPhaseCounter: map[string]int{},
|
||||
phaseDirty: map[string]struct{}{},
|
||||
stepPhaseDirty: map[string]struct{}{},
|
||||
}
|
||||
watcher.inc(tc.app, tc.op)
|
||||
assert.Equal(t, tc.wantPC, watcher.phaseCounter)
|
||||
assert.Equal(t, tc.wantSC, watcher.stepPhaseCounter)
|
||||
assert.Equal(t, tc.wantPD, watcher.phaseDirty)
|
||||
assert.Equal(t, tc.wantSD, watcher.stepPhaseDirty)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Idempotence", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
watcher := &applicationMetricsWatcher{
|
||||
phaseCounter: map[string]int{},
|
||||
stepPhaseCounter: map[string]int{},
|
||||
phaseDirty: map[string]struct{}{},
|
||||
stepPhaseDirty: map[string]struct{}{},
|
||||
}
|
||||
watcher.inc(appRunning, 1)
|
||||
watcher.inc(appRunning, 1)
|
||||
assert.Equal(t, map[string]int{"running": 2}, watcher.phaseCounter)
|
||||
assert.Equal(t, map[string]struct{}{"running": {}}, watcher.phaseDirty)
|
||||
})
|
||||
|
||||
t.Run("Report should clear dirty flags", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
watcher := &applicationMetricsWatcher{
|
||||
phaseCounter: map[string]int{"running": 1},
|
||||
stepPhaseCounter: map[string]int{"apply-component/succeeded#": 1},
|
||||
phaseDirty: map[string]struct{}{"running": {}},
|
||||
stepPhaseDirty: map[string]struct{}{"apply-component/succeeded#": {}},
|
||||
}
|
||||
watcher.report()
|
||||
assert.Empty(t, watcher.phaseDirty)
|
||||
assert.Empty(t, watcher.stepPhaseDirty)
|
||||
assert.Equal(t, map[string]int{"running": 1}, watcher.phaseCounter)
|
||||
assert.Equal(t, map[string]int{"apply-component/succeeded#": 1}, watcher.stepPhaseCounter)
|
||||
})
|
||||
|
||||
t.Run("getPhase helper function", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
watcher := &applicationMetricsWatcher{}
|
||||
assert.Equal(t, "-", watcher.getPhase(""))
|
||||
assert.Equal(t, "running", watcher.getPhase("running"))
|
||||
})
|
||||
|
||||
t.Run("getApp helper function", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
watcher := &applicationMetricsWatcher{}
|
||||
inputApp := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "test-ns",
|
||||
},
|
||||
Status: common.AppStatus{
|
||||
Phase: common.ApplicationRunning,
|
||||
},
|
||||
}
|
||||
resultApp := watcher.getApp(inputApp)
|
||||
assert.NotNil(t, resultApp)
|
||||
assert.Equal(t, "test-app", resultApp.Name)
|
||||
assert.Equal(t, "test-ns", resultApp.Namespace)
|
||||
assert.Equal(t, common.ApplicationRunning, resultApp.Status.Phase)
|
||||
})
|
||||
}
|
||||
|
|
@ -17,13 +17,19 @@
|
|||
package velaql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseVelaQL(t *testing.T) {
|
||||
t.Parallel()
|
||||
testcases := []struct {
|
||||
ql string
|
||||
query QueryView
|
||||
|
|
@ -67,19 +73,22 @@ func TestParseVelaQL(t *testing.T) {
|
|||
err: nil,
|
||||
}}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
q, err := ParseVelaQL(testcase.ql)
|
||||
assert.Equal(t, testcase.err != nil, err != nil)
|
||||
if err == nil {
|
||||
assert.Equal(t, testcase.query.View, q.View)
|
||||
assert.Equal(t, testcase.query.Export, q.Export)
|
||||
} else {
|
||||
assert.Equal(t, testcase.err.Error(), err.Error())
|
||||
}
|
||||
for i, testcase := range testcases {
|
||||
t.Run(fmt.Sprintf("testcase-%d", i), func(t *testing.T) {
|
||||
q, err := ParseVelaQL(testcase.ql)
|
||||
if testcase.err != nil {
|
||||
assert.EqualError(t, testcase.err, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testcase.query.View, q.View)
|
||||
assert.Equal(t, testcase.query.Export, q.Export)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseParameter(t *testing.T) {
|
||||
t.Parallel()
|
||||
testcases := []struct {
|
||||
parameter string
|
||||
parameterMap map[string]interface{}
|
||||
|
|
@ -122,15 +131,114 @@ func TestParseParameter(t *testing.T) {
|
|||
err: nil,
|
||||
}}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
result, err := ParseParameter(testcase.parameter)
|
||||
assert.Equal(t, testcase.err != nil, err != nil)
|
||||
if err == nil {
|
||||
for k, v := range result {
|
||||
assert.Equal(t, testcase.parameterMap[k], v)
|
||||
for i, testcase := range testcases {
|
||||
t.Run(fmt.Sprintf("testcase-%d", i), func(t *testing.T) {
|
||||
result, err := ParseParameter(testcase.parameter)
|
||||
if testcase.err != nil {
|
||||
assert.EqualError(t, testcase.err, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
for k, v := range result {
|
||||
assert.Equal(t, testcase.parameterMap[k], v)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert.Equal(t, testcase.err.Error(), err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseVelaQLFromPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
testdataDir := "testdata"
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedExport string
|
||||
expectError bool
|
||||
errorContains string
|
||||
}{
|
||||
{
|
||||
name: "Simple valid CUE file with export field",
|
||||
path: filepath.Join(testdataDir, "simple-valid.cue"),
|
||||
expectedExport: "output.message",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Simple valid CUE file without export field",
|
||||
path: filepath.Join(testdataDir, "simple-no-export.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Nonexistent file path",
|
||||
path: filepath.Join(testdataDir, "nonexistent.cue"),
|
||||
expectError: true,
|
||||
errorContains: "read view file from",
|
||||
},
|
||||
{
|
||||
name: "Empty file path",
|
||||
path: "",
|
||||
expectError: true,
|
||||
errorContains: "read view file from",
|
||||
},
|
||||
{
|
||||
name: "Invalid CUE content",
|
||||
path: filepath.Join(testdataDir, "invalid-cue-content.cue"),
|
||||
expectError: true,
|
||||
errorContains: "error when parsing view",
|
||||
},
|
||||
{
|
||||
name: "File with invalid export type - should fallback to default",
|
||||
path: filepath.Join(testdataDir, "invalid-export.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Empty CUE file",
|
||||
path: filepath.Join(testdataDir, "empty.cue"),
|
||||
expectedExport: DefaultExportValue,
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "File with leading/trailing whitespace",
|
||||
path: filepath.Join(testdataDir, "whitespace.cue"),
|
||||
expectedExport: "output.message",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "Relative path",
|
||||
path: "testdata/nonexistent.cue",
|
||||
expectError: true,
|
||||
errorContains: "read view file from",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result, err := ParseVelaQLFromPath(ctx, tc.path)
|
||||
|
||||
if tc.expectError {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
if tc.errorContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errorContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
|
||||
if tc.path != "" {
|
||||
expectedContent, readErr := os.ReadFile(tc.path)
|
||||
require.NoError(t, readErr)
|
||||
assert.Equal(t, string(expectedContent), result.View)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.expectedExport, result.Export)
|
||||
assert.Nil(t, result.Parameter)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
invalid cue syntax [
|
||||
missing: colon
|
||||
invalid: brackets
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
||||
export: 123 // Invalid export type (should be string)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
||||
export: "output.message"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
|
||||
parameter: {
|
||||
name: "test"
|
||||
}
|
||||
|
||||
output: {
|
||||
message: "hello " + parameter.name
|
||||
}
|
||||
|
||||
export: "output.message"
|
||||
|
||||
|
|
@ -29,12 +29,132 @@ import (
|
|||
dynamicfake "k8s.io/client-go/dynamic/fake"
|
||||
|
||||
"cuelang.org/go/cue/errors"
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/controller/core.oam.dev/v1beta1/core"
|
||||
)
|
||||
|
||||
func TestValidateDefinitionRevision(t *testing.T) {
|
||||
t.Parallel()
|
||||
scheme := runtime.NewScheme()
|
||||
v1beta1.AddToScheme(scheme)
|
||||
|
||||
baseCompDef := &v1beta1.ComponentDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-def",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ComponentDefinitionSpec{
|
||||
Workload: common.WorkloadTypeDescriptor{
|
||||
Definition: common.WorkloadGVK{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
Schematic: &common.Schematic{
|
||||
CUE: &common.CUE{
|
||||
Template: `
|
||||
output: {
|
||||
apiVersion: "apps/v1"
|
||||
kind: "Deployment"
|
||||
metadata: name: context.name
|
||||
}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedDefRev, _, err := core.GatherRevisionInfo(baseCompDef)
|
||||
assert.NoError(t, err, "Setup: failed to gather revision info")
|
||||
expectedDefRev.Name = "test-def-v1"
|
||||
expectedDefRev.Namespace = "default"
|
||||
|
||||
mismatchedHashDefRev := expectedDefRev.DeepCopy()
|
||||
mismatchedHashDefRev.Spec.RevisionHash = "different-hash"
|
||||
|
||||
mismatchedSpecDefRev := expectedDefRev.DeepCopy()
|
||||
mismatchedSpecDefRev.Spec.ComponentDefinition.Spec.Workload.Definition.Kind = "StatefulSet"
|
||||
|
||||
// tweakedCompDef := baseCompDef.DeepCopy()
|
||||
// tweakedCompDef.Spec.Schematic.CUE.Template = `
|
||||
// output: {
|
||||
// apiVersion: "apps/v1"
|
||||
// kind: "Deployment"
|
||||
// metadata: name: context.name
|
||||
// // a tweak
|
||||
// }`
|
||||
testCases := map[string]struct {
|
||||
def runtime.Object
|
||||
defRevName types.NamespacedName
|
||||
existingObjs []runtime.Object
|
||||
expectErr bool
|
||||
expectedErrContains string
|
||||
}{
|
||||
"Success with matching definition revision": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{expectedDefRev},
|
||||
expectErr: false,
|
||||
},
|
||||
"Success when definition revision does not exist": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{},
|
||||
expectErr: false,
|
||||
},
|
||||
"Failure with revision hash mismatch": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{mismatchedHashDefRev},
|
||||
expectErr: true,
|
||||
expectedErrContains: "the definition's spec is different with existing definitionRevision's spec",
|
||||
},
|
||||
"Failure with spec mismatch (DeepEqual)": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "test-def-v1", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{mismatchedSpecDefRev},
|
||||
expectErr: true,
|
||||
expectedErrContains: "the definition's spec is different with existing definitionRevision's spec",
|
||||
},
|
||||
"Failure with invalid definition revision name": {
|
||||
def: baseCompDef,
|
||||
defRevName: types.NamespacedName{Name: "invalid!name", Namespace: "default"},
|
||||
existingObjs: []runtime.Object{},
|
||||
expectErr: true,
|
||||
expectedErrContains: "invalid definitionRevision name",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
cli := fake.NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithRuntimeObjects(tc.existingObjs...).
|
||||
Build()
|
||||
|
||||
err := ValidateDefinitionRevision(context.Background(), cli, tc.def, tc.defRevName)
|
||||
|
||||
if tc.expectErr {
|
||||
assert.Error(t, err)
|
||||
if tc.expectedErrContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.expectedErrContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCueTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
cueTemplate string
|
||||
want error
|
||||
|
|
@ -66,19 +186,31 @@ func TestValidateCueTemplate(t *testing.T) {
|
|||
}`,
|
||||
want: errors.New("output.hello: reference \"world\" not found"),
|
||||
},
|
||||
"emptyCueTemp": {
|
||||
cueTemplate: "",
|
||||
want: nil,
|
||||
},
|
||||
"malformedCueTemp": {
|
||||
cueTemplate: "output: { metadata: { name: context.name, label: context.label, annotation: \"default\" }, hello: world ",
|
||||
want: errors.New("expected '}', found 'EOF'"),
|
||||
},
|
||||
}
|
||||
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateCueTemplate(cs.cueTemplate)
|
||||
if diff := cmp.Diff(cs.want, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nValidateCueTemplate: -want , +got \n%s\n", cs.want, diff)
|
||||
if cs.want != nil {
|
||||
assert.EqualError(t, cs.want, err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCuexTemplate(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
cueTemplate string
|
||||
want error
|
||||
|
|
@ -164,15 +296,19 @@ func TestValidateCuexTemplate(t *testing.T) {
|
|||
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateCuexTemplate(context.Background(), cs.cueTemplate)
|
||||
if diff := cmp.Diff(cs.want, err, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nValidateCueTemplate: -want , +got \n%s\n", cs.want, diff)
|
||||
if cs.want != nil {
|
||||
assert.Equal(t, cs.want.Error(), err.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSemanticVersion(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
version string
|
||||
want error
|
||||
|
|
@ -192,18 +328,20 @@ func TestValidateSemanticVersion(t *testing.T) {
|
|||
}
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateSemanticVersion(cs.version)
|
||||
if cs.want != nil {
|
||||
assert.Equal(t, err.Error(), cs.want.Error())
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, cs.want, err.Error())
|
||||
} else {
|
||||
assert.Equal(t, err, cs.want)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMultipleDefVersionsNotPresent(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]struct {
|
||||
version string
|
||||
revisionName string
|
||||
|
|
@ -227,11 +365,13 @@ func TestValidateMultipleDefVersionsNotPresent(t *testing.T) {
|
|||
}
|
||||
for caseName, cs := range cases {
|
||||
t.Run(caseName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := ValidateMultipleDefVersionsNotPresent(cs.version, cs.revisionName, "ComponentDefinition")
|
||||
if cs.want != nil {
|
||||
assert.Equal(t, err.Error(), cs.want.Error())
|
||||
assert.Error(t, err)
|
||||
assert.EqualError(t, cs.want, err.Error())
|
||||
} else {
|
||||
assert.Equal(t, err, cs.want)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ func setupClient(ctx context.Context, t *testing.T) client.Client {
|
|||
}
|
||||
|
||||
func TestParser(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
|
|
@ -106,7 +107,67 @@ func TestParser(t *testing.T) {
|
|||
r.Equal(act.Phase, "Wait")
|
||||
}
|
||||
|
||||
func TestRenderComponent(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
cuectx := cuecontext.New()
|
||||
cli := setupClient(ctx, t)
|
||||
|
||||
v := cuectx.CompileString(`$params: {
|
||||
value: {
|
||||
name: "test-render",
|
||||
type: "webservice",
|
||||
}
|
||||
}`)
|
||||
r.NoError(v.Err())
|
||||
|
||||
mockComponentRender := func(ctx context.Context, comp common.ApplicationComponent, patcher *cue.Value, clusterName string, overrideNamespace string) (*unstructured.Unstructured, []*unstructured.Unstructured, error) {
|
||||
r.Equal("test-render", comp.Name)
|
||||
workload := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-workload",
|
||||
},
|
||||
},
|
||||
}
|
||||
trait := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-trait",
|
||||
"labels": map[string]interface{}{
|
||||
"trait.oam.dev/resource": "mytrait",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return workload, []*unstructured.Unstructured{trait}, nil
|
||||
}
|
||||
|
||||
res, err := RenderComponent(ctx, &oamprovidertypes.Params[cue.Value]{
|
||||
Params: v,
|
||||
RuntimeParams: oamprovidertypes.RuntimeParams{
|
||||
KubeClient: cli,
|
||||
ComponentRender: mockComponentRender,
|
||||
},
|
||||
})
|
||||
r.NoError(err)
|
||||
|
||||
output, err := res.LookupPath(cue.ParsePath("$returns.output.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal("test-workload", output)
|
||||
|
||||
outputs, err := res.LookupPath(cue.ParsePath("$returns.outputs.mytrait.metadata.name")).String()
|
||||
r.NoError(err)
|
||||
r.Equal("test-workload", outputs)
|
||||
}
|
||||
|
||||
func TestLoadComponent(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
|
|
@ -171,6 +232,7 @@ func TestLoadComponent(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadComponentInOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := require.New(t)
|
||||
ctx := context.Background()
|
||||
act := &mock.Action{}
|
||||
|
|
|
|||
|
|
@ -15,3 +15,317 @@
|
|||
*/
|
||||
|
||||
package query
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
querytypes "github.com/oam-dev/kubevela/pkg/utils/types"
|
||||
)
|
||||
|
||||
func TestBuildResourceArray(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Define common objects used across tests
|
||||
pod1 := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod1",
|
||||
},
|
||||
},
|
||||
}
|
||||
pod2 := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "pod2",
|
||||
"annotations": map[string]interface{}{
|
||||
oam.AnnotationPublishVersion: "v2.0.0-pod",
|
||||
oam.AnnotationDeployVersion: "rev2-pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deployment := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-app",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Define common tree nodes
|
||||
parentWorkloadNode := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "my-app",
|
||||
Namespace: "default",
|
||||
Object: deployment,
|
||||
}
|
||||
|
||||
pod1Node := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: "pod1",
|
||||
Namespace: "default",
|
||||
Object: pod1,
|
||||
}
|
||||
|
||||
pod2Node := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
Name: "pod2",
|
||||
Namespace: "default",
|
||||
Object: pod2,
|
||||
}
|
||||
|
||||
replicaSetNode := &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
LeafNodes: []*querytypes.ResourceTreeNode{pod1Node, pod2Node},
|
||||
}
|
||||
|
||||
// Define test cases
|
||||
testCases := map[string]struct {
|
||||
res querytypes.AppliedResource
|
||||
parent *querytypes.ResourceTreeNode
|
||||
node *querytypes.ResourceTreeNode
|
||||
kind string
|
||||
apiVersion string
|
||||
expected []querytypes.ResourceItem
|
||||
}{
|
||||
"simple case with one matching pod": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: pod1Node,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "my-app",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"nested case with multiple matching pods": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
PublishVersion: "v2.0.0",
|
||||
DeployVersion: "rev2",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: replicaSetNode,
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v2.0.0",
|
||||
DeployVersion: "rev2",
|
||||
},
|
||||
{
|
||||
Cluster: "remote",
|
||||
Component: "my-comp-2",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod2,
|
||||
PublishVersion: "v2.0.0-pod", // From annotation
|
||||
DeployVersion: "rev2-pod", // From annotation
|
||||
},
|
||||
},
|
||||
},
|
||||
"no matching nodes": {
|
||||
res: querytypes.AppliedResource{},
|
||||
parent: parentWorkloadNode,
|
||||
node: pod1Node,
|
||||
kind: "Service",
|
||||
apiVersion: "v1",
|
||||
expected: nil,
|
||||
},
|
||||
"empty node": {
|
||||
res: querytypes.AppliedResource{},
|
||||
parent: parentWorkloadNode,
|
||||
node: &querytypes.ResourceTreeNode{},
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: nil,
|
||||
},
|
||||
"complex tree with mixed resources": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: &querytypes.ResourceTreeNode{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
LeafNodes: []*querytypes.ResourceTreeNode{
|
||||
pod1Node,
|
||||
{
|
||||
APIVersion: "v1",
|
||||
Kind: "Service",
|
||||
Name: "my-service",
|
||||
},
|
||||
pod2Node,
|
||||
},
|
||||
},
|
||||
kind: "Pod",
|
||||
apiVersion: "v1",
|
||||
expected: []querytypes.ResourceItem{
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod1,
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
Workload: querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "ReplicaSet",
|
||||
Name: "my-app-rs",
|
||||
Namespace: "default",
|
||||
},
|
||||
Object: pod2,
|
||||
PublishVersion: "v2.0.0-pod",
|
||||
DeployVersion: "rev2-pod",
|
||||
},
|
||||
},
|
||||
},
|
||||
"case-insensitive matching": {
|
||||
res: querytypes.AppliedResource{
|
||||
Cluster: "local",
|
||||
Component: "my-comp",
|
||||
PublishVersion: "v1.0.0",
|
||||
DeployVersion: "rev1",
|
||||
},
|
||||
parent: parentWorkloadNode,
|
||||
node: pod1Node,
|
||||
kind: "pod",
|
||||
apiVersion: "V1",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := buildResourceArray(tc.res, tc.parent, tc.node, tc.kind, tc.apiVersion)
|
||||
assert.ElementsMatch(t, tc.expected, result, "The returned resource items should match the expected ones")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildResourceItem(t *testing.T) {
|
||||
t.Parallel()
|
||||
pod := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-pod",
|
||||
},
|
||||
},
|
||||
}
|
||||
podWithAnnotations := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "test-pod-annotated",
|
||||
"annotations": map[string]interface{}{
|
||||
oam.AnnotationPublishVersion: "v2.0.0-annotated",
|
||||
oam.AnnotationDeployVersion: "rev2-annotated",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
res := querytypes.AppliedResource{
|
||||
Cluster: "test-cluster",
|
||||
Component: "test-comp",
|
||||
PublishVersion: "v1.0.0-res",
|
||||
DeployVersion: "rev1-res",
|
||||
}
|
||||
workload := querytypes.Workload{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "test-workload",
|
||||
Namespace: "test-ns",
|
||||
}
|
||||
|
||||
t.Run("without annotations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
item := buildResourceItem(res, workload, pod)
|
||||
assert.Equal(t, "test-cluster", item.Cluster)
|
||||
assert.Equal(t, "test-comp", item.Component)
|
||||
assert.Equal(t, workload, item.Workload)
|
||||
assert.Equal(t, pod, item.Object)
|
||||
assert.Equal(t, "v1.0.0-res", item.PublishVersion)
|
||||
assert.Equal(t, "rev1-res", item.DeployVersion)
|
||||
})
|
||||
|
||||
t.Run("with annotations", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
item := buildResourceItem(res, workload, podWithAnnotations)
|
||||
assert.Equal(t, "test-cluster", item.Cluster)
|
||||
assert.Equal(t, "test-comp", item.Component)
|
||||
assert.Equal(t, workload, item.Workload)
|
||||
assert.Equal(t, podWithAnnotations, item.Object)
|
||||
assert.Equal(t, "v2.0.0-annotated", item.PublishVersion)
|
||||
assert.Equal(t, "rev2-annotated", item.DeployVersion)
|
||||
})
|
||||
|
||||
t.Run("annotation override", func(t *testing.T) {
|
||||
item := buildResourceItem(res, workload, podWithAnnotations)
|
||||
assert.Equal(t, "v2.0.0-annotated", item.PublishVersion)
|
||||
assert.Equal(t, "rev2-annotated", item.DeployVersion)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,114 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam/util"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var scheme *runtime.Scheme
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var definitionDir string
|
||||
var wd corev1beta1.WorkloadDefinition
|
||||
var addonNamespace = "test-addon"
|
||||
|
||||
func TestAppFile(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Cli Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true), zap.WriteTo(GinkgoWriter)))
|
||||
ctx := context.Background()
|
||||
By("bootstrapping test environment")
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "charts", "vela-core", "crds")},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(cfg).ToNot(BeNil())
|
||||
scheme = runtime.NewScheme()
|
||||
Expect(coreoam.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(clientgoscheme.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
Expect(crdv1.AddToScheme(scheme)).NotTo(HaveOccurred())
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(k8sClient).ToNot(BeNil())
|
||||
|
||||
definitionDir, err = system.GetCapabilityDir()
|
||||
Expect(err).Should(BeNil())
|
||||
Expect(os.MkdirAll(definitionDir, 0755)).Should(BeNil())
|
||||
|
||||
Expect(k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: addonNamespace}})).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
workloadData, err := os.ReadFile("testdata/workloadDef.yaml")
|
||||
Expect(err).Should(BeNil())
|
||||
|
||||
Expect(yaml.Unmarshal(workloadData, &wd)).Should(BeNil())
|
||||
|
||||
wd.Namespace = addonNamespace
|
||||
logf.Log.Info("Creating workload definition", "data", wd)
|
||||
Expect(k8sClient.Create(ctx, &wd)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
|
||||
def, err := os.ReadFile("testdata/terraform-aliyun-oss-workloadDefinition.yaml")
|
||||
Expect(err).Should(BeNil())
|
||||
var terraformDefinition corev1beta1.WorkloadDefinition
|
||||
Expect(yaml.Unmarshal(def, &terraformDefinition)).Should(BeNil())
|
||||
terraformDefinition.Namespace = addonNamespace
|
||||
logf.Log.Info("Creating workload definition", "data", terraformDefinition)
|
||||
Expect(k8sClient.Create(ctx, &terraformDefinition)).Should(SatisfyAny(BeNil(), &util.AlreadyExistMatcher{}))
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
_ = k8sClient.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: addonNamespace}})
|
||||
_ = k8sClient.Delete(context.Background(), &wd)
|
||||
err := testEnv.Stop()
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
|
@ -19,9 +19,9 @@ package appfile
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ import (
|
|||
"github.com/oam-dev/kubevela/pkg/utils/util"
|
||||
)
|
||||
|
||||
var _ = It("Test ApplyTerraform", func() {
|
||||
func TestApplyTerraform(t *testing.T) {
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "test-terraform-app"},
|
||||
Spec: v1beta1.ApplicationSpec{Components: []commontype.ApplicationComponent{{
|
||||
|
|
@ -46,27 +46,29 @@ var _ = It("Test ApplyTerraform", func() {
|
|||
Schema: scheme,
|
||||
}
|
||||
err := arg.SetConfig(cfg)
|
||||
Expect(err).Should(BeNil())
|
||||
assert.NoError(t, err)
|
||||
_, err = ApplyTerraform(app, k8sClient, ioStream, addonNamespace, arg)
|
||||
Expect(err).Should(BeNil())
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
var _ = Describe("Test generateSecretFromTerraformOutput", func() {
|
||||
func TestGenerateSecretFromTerraformOutput(t *testing.T) {
|
||||
var name = "test-addon-secret"
|
||||
It("namespace doesn't exist", func() {
|
||||
|
||||
t.Run("namespace doesn't exist", func(t *testing.T) {
|
||||
badNamespace := "a-not-existed-namespace"
|
||||
err := generateSecretFromTerraformOutput(k8sClient, "", name, badNamespace)
|
||||
Expect(err).Should(Equal(fmt.Errorf("namespace %s doesn't exist", badNamespace)))
|
||||
})
|
||||
It("valid output list", func() {
|
||||
rawOutput := "name=aaa\nage=1"
|
||||
err := generateSecretFromTerraformOutput(k8sClient, rawOutput, name, addonNamespace)
|
||||
Expect(err).Should(BeNil())
|
||||
assert.EqualError(t, err, fmt.Sprintf("namespace %s doesn't exist", badNamespace))
|
||||
})
|
||||
|
||||
It("invalid output list", func() {
|
||||
t.Run("valid output list", func(t *testing.T) {
|
||||
rawOutput := "name=aaa\nage=1"
|
||||
err := generateSecretFromTerraformOutput(k8sClient, rawOutput, name, addonNamespace)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("invalid output list", func(t *testing.T) {
|
||||
rawOutput := "name"
|
||||
err := generateSecretFromTerraformOutput(k8sClient, rawOutput, name, addonNamespace)
|
||||
Expect(err).Should(Equal(fmt.Errorf("terraform output isn't in the right format: %q", rawOutput)))
|
||||
assert.EqualError(t, err, fmt.Sprintf("terraform output isn't in the right format: %q", rawOutput))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,58 @@ import (
|
|||
"github.com/oam-dev/kubevela/references/appfile/template"
|
||||
)
|
||||
|
||||
func TestNewAppFile(t *testing.T) {
|
||||
appFile := NewAppFile()
|
||||
assert.NotNil(t, appFile)
|
||||
assert.NotNil(t, appFile.Services)
|
||||
assert.NotNil(t, appFile.Secrets)
|
||||
assert.Empty(t, appFile.Name)
|
||||
}
|
||||
|
||||
func TestJSONToYaml(t *testing.T) {
|
||||
t.Run("valid json", func(t *testing.T) {
|
||||
jsonData := `{"name":"test-app","services":{"my-svc":{"image":"nginx"}}}`
|
||||
appFile := NewAppFile()
|
||||
_, err := JSONToYaml([]byte(jsonData), appFile)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-app", appFile.Name)
|
||||
assert.Contains(t, appFile.Services, "my-svc")
|
||||
assert.Equal(t, "nginx", appFile.Services["my-svc"]["image"])
|
||||
})
|
||||
|
||||
t.Run("invalid json", func(t *testing.T) {
|
||||
jsonData := `{"name":"test-app"`
|
||||
appFile := NewAppFile()
|
||||
_, err := JSONToYaml([]byte(jsonData), appFile)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadFromBytes(t *testing.T) {
|
||||
t.Run("valid yaml", func(t *testing.T) {
|
||||
yamlData := `
|
||||
name: test-yaml-app
|
||||
services:
|
||||
main:
|
||||
image: httpd
|
||||
`
|
||||
appFile, err := LoadFromBytes([]byte(yamlData))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-yaml-app", appFile.Name)
|
||||
assert.Len(t, appFile.Services, 1)
|
||||
assert.Equal(t, "httpd", appFile.Services["main"]["image"])
|
||||
})
|
||||
|
||||
t.Run("valid json", func(t *testing.T) {
|
||||
jsonData := `{"name":"test-json-app","services":{"sidecar":{"image":"redis"}}}`
|
||||
appFile, err := LoadFromBytes([]byte(jsonData))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-json-app", appFile.Name)
|
||||
assert.Len(t, appFile.Services, 1)
|
||||
assert.Equal(t, "redis", appFile.Services["sidecar"]["image"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildOAMApplication2(t *testing.T) {
|
||||
expectNs := "test-ns"
|
||||
|
||||
|
|
|
|||
|
|
@ -36,3 +36,85 @@ func TestGetType(t *testing.T) {
|
|||
got = svc2.GetType()
|
||||
assert.Equal(t, workload2, got)
|
||||
}
|
||||
|
||||
func TestGetUserConfigName(t *testing.T) {
|
||||
t.Run("config name exists", func(t *testing.T) {
|
||||
svc := Service{"config": "my-config"}
|
||||
assert.Equal(t, "my-config", svc.GetUserConfigName())
|
||||
})
|
||||
|
||||
t.Run("config name does not exist", func(t *testing.T) {
|
||||
svc := Service{"image": "nginx"}
|
||||
assert.Equal(t, "", svc.GetUserConfigName())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetApplicationConfig(t *testing.T) {
|
||||
svc := Service{
|
||||
"image": "nginx",
|
||||
"port": 80,
|
||||
"type": "webservice",
|
||||
"build": "./",
|
||||
"config": "my-config",
|
||||
}
|
||||
|
||||
config := svc.GetApplicationConfig()
|
||||
|
||||
assert.Contains(t, config, "image")
|
||||
assert.Contains(t, config, "port")
|
||||
assert.NotContains(t, config, "type")
|
||||
assert.NotContains(t, config, "build")
|
||||
assert.NotContains(t, config, "config")
|
||||
assert.Len(t, config, 2)
|
||||
}
|
||||
|
||||
func TestToStringSlice(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
input interface{}
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "string",
|
||||
input: "one",
|
||||
expected: []string{"one"},
|
||||
},
|
||||
{
|
||||
name: "[]string",
|
||||
input: []string{"one", "two"},
|
||||
expected: []string{"one", "two"},
|
||||
},
|
||||
{
|
||||
name: "[]interface{} of strings",
|
||||
input: []interface{}{"one", "two"},
|
||||
expected: []string{"one", "two"},
|
||||
},
|
||||
{
|
||||
name: "[]interface{} of mixed types",
|
||||
input: []interface{}{"one", 2, "three"},
|
||||
expected: []string{"one", "three"},
|
||||
},
|
||||
{
|
||||
name: "nil input",
|
||||
input: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "empty []string",
|
||||
input: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "other type (int)",
|
||||
input: 123,
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := toStringSlice(tc.input)
|
||||
assert.Equal(t, tc.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,18 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
"github.com/oam-dev/kubevela/references/appfile/api"
|
||||
"github.com/oam-dev/kubevela/references/appfile/template"
|
||||
)
|
||||
|
||||
func TestApplication(t *testing.T) {
|
||||
yamlNormal := `name: myapp
|
||||
const (
|
||||
yamlNormal = `name: myapp
|
||||
services:
|
||||
frontend:
|
||||
image: inanimate/echo-server
|
||||
|
|
@ -45,55 +49,207 @@ services:
|
|||
type: cloneset
|
||||
image: "back:v1"
|
||||
`
|
||||
yamlNoService := `name: myapp`
|
||||
yamlNoName := `services:
|
||||
yamlNoService = `name: myapp`
|
||||
yamlNoName = `services:
|
||||
frontend:
|
||||
image: inanimate/echo-server
|
||||
env:
|
||||
PORT: 8080`
|
||||
yamlTraitNotMap := `name: myapp
|
||||
yamlTraitNotMap = `name: myapp
|
||||
services:
|
||||
frontend:
|
||||
image: inanimate/echo-server
|
||||
env:
|
||||
PORT: 8080
|
||||
autoscaling: 10`
|
||||
)
|
||||
|
||||
cases := map[string]struct {
|
||||
raw string
|
||||
InValid bool
|
||||
InvalidReason error
|
||||
ExpName string
|
||||
ExpComponents []string
|
||||
WantWorkload string
|
||||
ExpWorkload map[string]interface{}
|
||||
ExpWorkloadType string
|
||||
ExpTraits map[string]map[string]interface{}
|
||||
func TestNewApplication(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
app := NewApplication(nil, tm)
|
||||
assert.NotNil(t, app)
|
||||
assert.Equal(t, tm, app.Tm)
|
||||
assert.NotNil(t, app.AppFile)
|
||||
|
||||
appfile := api.NewAppFile()
|
||||
appfile.Name = "test-app"
|
||||
app = NewApplication(appfile, tm)
|
||||
assert.NotNil(t, app)
|
||||
assert.Equal(t, "test-app", app.Name)
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
raw string
|
||||
expErr error
|
||||
addFake bool
|
||||
}{
|
||||
"normal case backend": {
|
||||
raw: yamlNormal,
|
||||
ExpName: "myapp",
|
||||
ExpComponents: []string{"backend", "frontend"},
|
||||
WantWorkload: "backend",
|
||||
ExpWorkload: map[string]interface{}{
|
||||
"image": "back:v1",
|
||||
},
|
||||
ExpWorkloadType: "cloneset",
|
||||
ExpTraits: map[string]map[string]interface{}{},
|
||||
"normal": {
|
||||
raw: yamlNormal,
|
||||
expErr: nil,
|
||||
},
|
||||
"normal case frontend": {
|
||||
raw: yamlNormal,
|
||||
ExpName: "myapp",
|
||||
ExpComponents: []string{"backend", "frontend"},
|
||||
WantWorkload: "frontend",
|
||||
ExpWorkload: map[string]interface{}{
|
||||
"no service": {
|
||||
raw: yamlNoService,
|
||||
expErr: errors.New("at least one service is required"),
|
||||
},
|
||||
"no name": {
|
||||
raw: yamlNoName,
|
||||
expErr: errors.New("name is required"),
|
||||
},
|
||||
"trait not map": {
|
||||
raw: yamlTraitNotMap,
|
||||
expErr: fmt.Errorf("trait autoscaling in 'frontend' must be map"),
|
||||
addFake: true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
if tc.addFake {
|
||||
tm.Templates["autoscaling"] = &template.Template{
|
||||
Captype: types.TypeTrait,
|
||||
}
|
||||
}
|
||||
app := NewApplication(nil, tm)
|
||||
err := yaml.Unmarshal([]byte(tc.raw), &app)
|
||||
assert.NoError(t, err)
|
||||
err = Validate(app)
|
||||
assert.Equal(t, tc.expErr, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetComponents(t *testing.T) {
|
||||
app := &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{Name: "c"},
|
||||
{Name: "a"},
|
||||
{Name: "b"},
|
||||
},
|
||||
},
|
||||
}
|
||||
comps := GetComponents(app)
|
||||
assert.Equal(t, []string{"a", "b", "c"}, comps)
|
||||
}
|
||||
|
||||
func TestGetServiceConfig(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
app := NewApplication(nil, tm)
|
||||
err := yaml.Unmarshal([]byte(yamlNormal), &app)
|
||||
assert.NoError(t, err)
|
||||
|
||||
tp, cfg := GetServiceConfig(app, "frontend")
|
||||
assert.Equal(t, "webservice", tp)
|
||||
assert.NotEmpty(t, cfg)
|
||||
assert.Contains(t, cfg, "image")
|
||||
|
||||
tp, cfg = GetServiceConfig(app, "backend")
|
||||
assert.Equal(t, "cloneset", tp)
|
||||
assert.NotEmpty(t, cfg)
|
||||
assert.Contains(t, cfg, "image")
|
||||
|
||||
tp, cfg = GetServiceConfig(app, "non-existent")
|
||||
assert.Equal(t, "", tp)
|
||||
assert.Empty(t, cfg)
|
||||
}
|
||||
|
||||
func TestGetApplicationSettings(t *testing.T) {
|
||||
app := &v1beta1.Application{
|
||||
Spec: v1beta1.ApplicationSpec{
|
||||
Components: []common.ApplicationComponent{
|
||||
{
|
||||
Name: "comp-1",
|
||||
Type: "worker",
|
||||
Properties: &runtime.RawExtension{
|
||||
Raw: []byte(`{"image":"my-image"}`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tp, settings := GetApplicationSettings(app, "comp-1")
|
||||
assert.Equal(t, "worker", tp)
|
||||
assert.Equal(t, map[string]interface{}{"image": "my-image"}, settings)
|
||||
|
||||
tp, settings = GetApplicationSettings(app, "non-existent")
|
||||
assert.Equal(t, "", tp)
|
||||
assert.Empty(t, settings)
|
||||
}
|
||||
|
||||
func TestGetWorkload(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
tm.Templates["autoscaling"] = &template.Template{Captype: types.TypeTrait}
|
||||
tm.Templates["rollout"] = &template.Template{Captype: types.TypeTrait}
|
||||
|
||||
app := NewApplication(nil, tm)
|
||||
err := yaml.Unmarshal([]byte(yamlNormal), &app)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
componentName string
|
||||
expWorkloadType string
|
||||
expWorkload map[string]interface{}
|
||||
}{
|
||||
"frontend": {
|
||||
componentName: "frontend",
|
||||
expWorkloadType: "webservice",
|
||||
expWorkload: map[string]interface{}{
|
||||
"image": "inanimate/echo-server",
|
||||
"env": map[string]interface{}{
|
||||
"PORT": float64(8080),
|
||||
},
|
||||
},
|
||||
ExpWorkloadType: "webservice",
|
||||
ExpTraits: map[string]map[string]interface{}{
|
||||
},
|
||||
"backend": {
|
||||
componentName: "backend",
|
||||
expWorkloadType: "cloneset",
|
||||
expWorkload: map[string]interface{}{
|
||||
"image": "back:v1",
|
||||
},
|
||||
},
|
||||
"non-existent": {
|
||||
componentName: "non-existent",
|
||||
expWorkloadType: "",
|
||||
expWorkload: map[string]interface{}{},
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
workloadType, workload := GetWorkload(app, tc.componentName)
|
||||
assert.Equal(t, tc.expWorkloadType, workloadType)
|
||||
assert.Equal(t, tc.expWorkload, workload)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTraits(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
tm.Templates["autoscaling"] = &template.Template{Captype: types.TypeTrait}
|
||||
tm.Templates["rollout"] = &template.Template{Captype: types.TypeTrait}
|
||||
|
||||
app := NewApplication(nil, tm)
|
||||
err := yaml.Unmarshal([]byte(yamlNormal), &app)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test case with invalid trait format
|
||||
invalidTraitApp := NewApplication(nil, tm)
|
||||
err = yaml.Unmarshal([]byte(yamlTraitNotMap), &invalidTraitApp)
|
||||
assert.NoError(t, err)
|
||||
|
||||
testCases := map[string]struct {
|
||||
app *api.Application
|
||||
compName string
|
||||
exp map[string]map[string]interface{}
|
||||
expErr string
|
||||
}{
|
||||
"frontend traits": {
|
||||
app: app,
|
||||
compName: "frontend",
|
||||
exp: map[string]map[string]interface{}{
|
||||
"autoscaling": {
|
||||
"max": float64(10),
|
||||
"min": float64(1),
|
||||
|
|
@ -104,50 +260,33 @@ services:
|
|||
},
|
||||
},
|
||||
},
|
||||
"no component": {
|
||||
raw: yamlNoService,
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: errors.New("at least one service is required"),
|
||||
"backend traits (none)": {
|
||||
app: app,
|
||||
compName: "backend",
|
||||
exp: map[string]map[string]interface{}{},
|
||||
},
|
||||
"no name": {
|
||||
raw: yamlNoName,
|
||||
ExpName: "",
|
||||
InValid: true,
|
||||
InvalidReason: errors.New("name is required"),
|
||||
"non-existent component": {
|
||||
app: app,
|
||||
compName: "non-existent",
|
||||
exp: map[string]map[string]interface{}{},
|
||||
},
|
||||
"trait must be map": {
|
||||
raw: yamlTraitNotMap,
|
||||
ExpTraits: map[string]map[string]interface{}{
|
||||
"autoscaling": {},
|
||||
},
|
||||
ExpName: "myapp",
|
||||
InValid: true,
|
||||
InvalidReason: fmt.Errorf("trait autoscaling in 'frontend' must be map"),
|
||||
"invalid trait format": {
|
||||
app: invalidTraitApp,
|
||||
compName: "frontend",
|
||||
exp: nil,
|
||||
expErr: "autoscaling is trait, but with invalid format float64, should be map[string]interface{}",
|
||||
},
|
||||
}
|
||||
|
||||
for caseName, c := range cases {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
for k := range c.ExpTraits {
|
||||
tm.Templates[k] = &template.Template{
|
||||
Captype: types.TypeTrait,
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
traits, err := GetTraits(tc.app, tc.compName)
|
||||
if tc.expErr != "" {
|
||||
assert.EqualError(t, err, tc.expErr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.exp, traits)
|
||||
}
|
||||
}
|
||||
app := NewApplication(nil, tm)
|
||||
err := yaml.Unmarshal([]byte(c.raw), &app)
|
||||
assert.NoError(t, err, caseName)
|
||||
err = Validate(app)
|
||||
if c.InValid {
|
||||
assert.Equal(t, c.InvalidReason, err)
|
||||
continue
|
||||
}
|
||||
assert.Equal(t, c.ExpName, app.Name, caseName)
|
||||
workloadType, workload := GetWorkload(app, c.WantWorkload)
|
||||
assert.Equal(t, c.ExpWorkload, workload, caseName)
|
||||
assert.Equal(t, c.ExpWorkloadType, workloadType, caseName)
|
||||
traits, err := GetTraits(app, c.WantWorkload)
|
||||
assert.NoError(t, err, caseName)
|
||||
assert.Equal(t, c.ExpTraits, traits, caseName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
crdv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
coreoam "github.com/oam-dev/kubevela/apis/core.oam.dev"
|
||||
corev1beta1 "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/utils/system"
|
||||
)
|
||||
|
||||
var cfg *rest.Config
|
||||
var scheme *runtime.Scheme
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var definitionDir string
|
||||
var wd corev1beta1.WorkloadDefinition
|
||||
var addonNamespace = "test-addon"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
logf.SetLogger(zap.New(zap.UseDevMode(true)))
|
||||
ctx := context.Background()
|
||||
|
||||
useExistCluster := false
|
||||
testEnv = &envtest.Environment{
|
||||
ControlPlaneStartTimeout: time.Minute,
|
||||
ControlPlaneStopTimeout: time.Minute,
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "charts", "vela-core", "crds")},
|
||||
UseExistingCluster: &useExistCluster,
|
||||
}
|
||||
|
||||
var err error
|
||||
cfg, err = testEnv.Start()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to start test environment: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// cleanupAndExit stops the test environment and exits with the given code.
|
||||
cleanupAndExit := func(code int) {
|
||||
// Clean up other resources before stopping the environment
|
||||
if k8sClient != nil {
|
||||
_ = k8sClient.Delete(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: addonNamespace}})
|
||||
_ = k8sClient.Delete(context.Background(), &wd)
|
||||
}
|
||||
if err := testEnv.Stop(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to stop test environment: %v\n", err)
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
scheme = runtime.NewScheme()
|
||||
if err := coreoam.AddToScheme(scheme); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to add coreoam to scheme: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
if err := clientgoscheme.AddToScheme(scheme); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to add clientgoscheme to scheme: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
if err := crdv1.AddToScheme(scheme); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to add crdv1 to scheme: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create k8sClient: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
|
||||
definitionDir, err = system.GetCapabilityDir()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get capability dir: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
if err := os.MkdirAll(definitionDir, 0755); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create capability dir: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
|
||||
if err := k8sClient.Create(ctx, &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: addonNamespace}}); err != nil {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create test namespace: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
}
|
||||
|
||||
workloadData, err := os.ReadFile("testdata/workloadDef.yaml")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to read workloadDef.yaml: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(workloadData, &wd); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to unmarshal workloadDef.yaml: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
|
||||
wd.Namespace = addonNamespace
|
||||
if err := k8sClient.Create(ctx, &wd); err != nil {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create workload definition: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
}
|
||||
|
||||
def, err := os.ReadFile("testdata/terraform-aliyun-oss-workloadDefinition.yaml")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to read terraform-aliyun-oss-workloadDefinition.yaml: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
var terraformDefinition corev1beta1.WorkloadDefinition
|
||||
if err := yaml.Unmarshal(def, &terraformDefinition); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to unmarshal terraformDefinition: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
terraformDefinition.Namespace = addonNamespace
|
||||
if err := k8sClient.Create(ctx, &terraformDefinition); err != nil {
|
||||
if !errors.IsAlreadyExists(err) {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create terraform workload definition: %v\n", err)
|
||||
cleanupAndExit(1)
|
||||
}
|
||||
}
|
||||
|
||||
code := m.Run()
|
||||
cleanupAndExit(code)
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/oam-dev/kubevela/references/appfile/api"
|
||||
"github.com/oam-dev/kubevela/references/appfile/template"
|
||||
)
|
||||
|
||||
func TestSetWorkload(t *testing.T) {
|
||||
tm := template.NewFakeTemplateManager()
|
||||
|
||||
t.Run("app is nil", func(t *testing.T) {
|
||||
err := SetWorkload(nil, "comp", "worker", nil)
|
||||
assert.EqualError(t, err, errorAppNilPointer.Error())
|
||||
})
|
||||
|
||||
t.Run("add new component", func(t *testing.T) {
|
||||
app := NewApplication(nil, tm)
|
||||
app.Name = "test-app"
|
||||
workloadData := map[string]interface{}{"image": "test-image", "cmd": []string{"sleep", "1000"}}
|
||||
err := SetWorkload(app, "my-comp", "worker", workloadData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, app.Services, 1)
|
||||
svc, ok := app.Services["my-comp"]
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, svc)
|
||||
assert.Equal(t, "worker", svc["type"])
|
||||
assert.Equal(t, "test-image", svc["image"])
|
||||
assert.Equal(t, []string{"sleep", "1000"}, svc["cmd"])
|
||||
})
|
||||
|
||||
t.Run("update existing component", func(t *testing.T) {
|
||||
app := NewApplication(nil, tm)
|
||||
app.Name = "test-app"
|
||||
app.Services["my-comp"] = api.Service{
|
||||
"type": "worker",
|
||||
"image": "initial-image",
|
||||
}
|
||||
|
||||
updatedWorkloadData := map[string]interface{}{"image": "updated-image", "port": 8080}
|
||||
err := SetWorkload(app, "my-comp", "webservice", updatedWorkloadData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, app.Services, 1)
|
||||
svc, ok := app.Services["my-comp"]
|
||||
assert.True(t, ok)
|
||||
assert.NotNil(t, svc)
|
||||
assert.Equal(t, "webservice", svc["type"])
|
||||
assert.Equal(t, "updated-image", svc["image"])
|
||||
assert.Equal(t, 8080, svc["port"])
|
||||
})
|
||||
|
||||
t.Run("add to existing services", func(t *testing.T) {
|
||||
app := NewApplication(nil, tm)
|
||||
app.Name = "test-app"
|
||||
app.Services["comp-1"] = api.Service{
|
||||
"type": "worker",
|
||||
}
|
||||
|
||||
workloadData := map[string]interface{}{"image": "test-image"}
|
||||
err := SetWorkload(app, "comp-2", "task", workloadData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Len(t, app.Services, 2)
|
||||
assert.Contains(t, app.Services, "comp-1")
|
||||
assert.Contains(t, app.Services, "comp-2")
|
||||
svc2 := app.Services["comp-2"]
|
||||
assert.Equal(t, "task", svc2["type"])
|
||||
assert.Equal(t, "test-image", svc2["image"])
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package appfile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/pkg/oam"
|
||||
)
|
||||
|
||||
func TestCreateOrUpdateApplication(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1beta1.AddToScheme(scheme))
|
||||
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("create application", func(t *testing.T) {
|
||||
builder := fake.NewClientBuilder().WithScheme(scheme)
|
||||
fakeClient := builder.Build()
|
||||
err := CreateOrUpdateApplication(context.Background(), fakeClient, app)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var created v1beta1.Application
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(app), &created)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-app", created.Name)
|
||||
})
|
||||
|
||||
t.Run("update application", func(t *testing.T) {
|
||||
appToUpdate := app.DeepCopy()
|
||||
appToUpdate.SetAnnotations(map[string]string{"key": "val"})
|
||||
|
||||
builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(app)
|
||||
fakeClient := builder.Build()
|
||||
|
||||
err := CreateOrUpdateApplication(context.Background(), fakeClient, appToUpdate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var updated v1beta1.Application
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(app), &updated)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "val", updated.Annotations["key"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestCreateOrUpdateObjects(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, corev1.AddToScheme(scheme))
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
Data: map[string]string{
|
||||
"initial": "true",
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("create object", func(t *testing.T) {
|
||||
builder := fake.NewClientBuilder().WithScheme(scheme)
|
||||
fakeClient := builder.Build()
|
||||
objects := []oam.Object{cm}
|
||||
|
||||
err := CreateOrUpdateObjects(context.Background(), fakeClient, objects)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var created corev1.ConfigMap
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(cm), &created)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "true", created.Data["initial"])
|
||||
})
|
||||
|
||||
t.Run("update object", func(t *testing.T) {
|
||||
cmToUpdate := cm.DeepCopy()
|
||||
cmToUpdate.Data["initial"] = "false"
|
||||
|
||||
builder := fake.NewClientBuilder().WithScheme(scheme).WithObjects(cm)
|
||||
fakeClient := builder.Build()
|
||||
objects := []oam.Object{cmToUpdate}
|
||||
|
||||
err := CreateOrUpdateObjects(context.Background(), fakeClient, objects)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var updated corev1.ConfigMap
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(cm), &updated)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "false", updated.Data["initial"])
|
||||
})
|
||||
}
|
||||
|
||||
func TestRun(t *testing.T) {
|
||||
scheme := runtime.NewScheme()
|
||||
assert.NoError(t, v1beta1.AddToScheme(scheme))
|
||||
assert.NoError(t, corev1.AddToScheme(scheme))
|
||||
|
||||
app := &v1beta1.Application{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-app-run",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
cm := &corev1.ConfigMap{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "ConfigMap",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm-run",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
builder := fake.NewClientBuilder().WithScheme(scheme)
|
||||
fakeClient := builder.Build()
|
||||
|
||||
err := Run(context.Background(), fakeClient, app, []oam.Object{cm})
|
||||
assert.NoError(t, err)
|
||||
|
||||
var createdApp v1beta1.Application
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(app), &createdApp)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-app-run", createdApp.Name)
|
||||
|
||||
var createdCM corev1.ConfigMap
|
||||
err = fakeClient.Get(context.Background(), client.ObjectKeyFromObject(cm), &createdCM)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test-cm-run", createdCM.Name)
|
||||
}
|
||||
|
|
@ -18,6 +18,7 @@ package config
|
|||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -27,15 +28,82 @@ import (
|
|||
)
|
||||
|
||||
func TestString(t *testing.T) {
|
||||
c := Color("red")
|
||||
assert.Equal(t, c.String(), "#ff0000")
|
||||
testCases := map[string]struct {
|
||||
color Color
|
||||
expected string
|
||||
}{
|
||||
"named color": {
|
||||
color: "red",
|
||||
expected: "#ff0000",
|
||||
},
|
||||
"hex color": {
|
||||
color: "#aabbcc",
|
||||
expected: "#aabbcc",
|
||||
},
|
||||
"default color": {
|
||||
color: DefaultColor,
|
||||
expected: "-",
|
||||
},
|
||||
"invalid color": {
|
||||
color: "invalidColor",
|
||||
expected: "-",
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, tc.color.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColor(t *testing.T) {
|
||||
c1 := Color("#ff0000")
|
||||
assert.Equal(t, c1.Color(), tcell.GetColor("#ff0000"))
|
||||
assert.Equal(t, tcell.GetColor("#ff0000"), c1.Color())
|
||||
c2 := Color("red")
|
||||
assert.Equal(t, c2.Color(), tcell.GetColor("red").TrueColor())
|
||||
assert.Equal(t, tcell.GetColor("red").TrueColor(), c2.Color())
|
||||
c3 := Color(DefaultColor)
|
||||
assert.Equal(t, tcell.ColorDefault, c3.Color())
|
||||
}
|
||||
|
||||
func TestIsHex(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
color Color
|
||||
expected bool
|
||||
}{
|
||||
"is hex": {
|
||||
color: "#aabbcc",
|
||||
expected: true,
|
||||
},
|
||||
"not hex (too short)": {
|
||||
color: "#aabbc",
|
||||
expected: false,
|
||||
},
|
||||
"not hex (too long)": {
|
||||
color: "#aabbccd",
|
||||
expected: false,
|
||||
},
|
||||
"not hex (no #)": {
|
||||
color: "aabbcc",
|
||||
expected: false,
|
||||
},
|
||||
"not hex (named color)": {
|
||||
color: "red",
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, tc.color.isHex())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultTheme(t *testing.T) {
|
||||
theme := defaultTheme()
|
||||
assert.NotNil(t, theme)
|
||||
assert.Equal(t, Color("royalblue"), theme.Info.Title)
|
||||
assert.Equal(t, Color("green"), theme.Status.Healthy)
|
||||
assert.Equal(t, Color("red"), theme.Status.UnHealthy)
|
||||
}
|
||||
|
||||
func TestPersistentThemeConfig(t *testing.T) {
|
||||
|
|
@ -45,3 +113,101 @@ func TestPersistentThemeConfig(t *testing.T) {
|
|||
assert.Nil(t, err)
|
||||
assert.True(t, strings.Contains(string(bytes), "foo"))
|
||||
}
|
||||
|
||||
func TestMakeThemeConfigFileIfNotExist(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "vela-theme-test")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
originalHomePath := homePath
|
||||
originalThemeConfigFilePath := themeConfigFilePath
|
||||
homePath = tmpDir
|
||||
themeConfigFilePath = filepath.Join(tmpDir, themeHomeDirPath, themeConfigFile)
|
||||
defer func() {
|
||||
homePath = originalHomePath
|
||||
themeConfigFilePath = originalThemeConfigFilePath
|
||||
}()
|
||||
|
||||
t.Run("should create file if it does not exist", func(t *testing.T) {
|
||||
os.Remove(themeConfigFilePath)
|
||||
|
||||
exists := makeThemeConfigFileIfNotExist()
|
||||
assert.False(t, exists, "should return false as file was created")
|
||||
|
||||
_, err := os.Stat(themeConfigFilePath)
|
||||
assert.NoError(t, err, "expected theme config file to be created")
|
||||
content, err := os.ReadFile(themeConfigFilePath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "name : "+DefaultTheme, string(content))
|
||||
})
|
||||
|
||||
t.Run("should not modify file if it already exists", func(t *testing.T) {
|
||||
customContent := "name : custom"
|
||||
err := os.WriteFile(themeConfigFilePath, []byte(customContent), 0600)
|
||||
assert.NoError(t, err)
|
||||
|
||||
exists := makeThemeConfigFileIfNotExist()
|
||||
assert.True(t, exists, "should return true as file already exists")
|
||||
|
||||
content, err := os.ReadFile(themeConfigFilePath)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, customContent, string(content))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadThemeConfig(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "vela-theme-test-load")
|
||||
assert.NoError(t, err)
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
originalHomePath := homePath
|
||||
originalThemeConfigFilePath := themeConfigFilePath
|
||||
originalDiyThemeDirPath := diyThemeDirPath
|
||||
homePath = tmpDir
|
||||
themeConfigFilePath = filepath.Join(tmpDir, themeHomeDirPath, themeConfigFile)
|
||||
diyThemeDirPath = filepath.Join(tmpDir, themeHomeDirPath, diyThemeDir)
|
||||
defer func() {
|
||||
homePath = originalHomePath
|
||||
themeConfigFilePath = originalThemeConfigFilePath
|
||||
diyThemeDirPath = originalDiyThemeDirPath
|
||||
}()
|
||||
|
||||
ThemeMap["custom"] = ThemeConfig{
|
||||
Info: struct {
|
||||
Title Color `yaml:"title"`
|
||||
Text Color `yaml:"text"`
|
||||
}{Title: "custom-title"},
|
||||
}
|
||||
defer delete(ThemeMap, "custom")
|
||||
|
||||
t.Run("config file not exist", func(t *testing.T) {
|
||||
os.Remove(themeConfigFilePath)
|
||||
cfg := LoadThemeConfig()
|
||||
assert.Equal(t, defaultTheme().Info.Title, cfg.Info.Title)
|
||||
})
|
||||
|
||||
t.Run("config file with default theme", func(t *testing.T) {
|
||||
PersistentThemeConfig(DefaultTheme)
|
||||
cfg := LoadThemeConfig()
|
||||
assert.Equal(t, defaultTheme().Info.Title, cfg.Info.Title)
|
||||
})
|
||||
|
||||
t.Run("config file with custom theme", func(t *testing.T) {
|
||||
PersistentThemeConfig("custom")
|
||||
cfg := LoadThemeConfig()
|
||||
assert.Equal(t, Color("custom-title"), cfg.Info.Title)
|
||||
})
|
||||
|
||||
t.Run("config file with unknown theme", func(t *testing.T) {
|
||||
PersistentThemeConfig("unknown")
|
||||
cfg := LoadThemeConfig()
|
||||
assert.Equal(t, defaultTheme().Info.Title, cfg.Info.Title)
|
||||
})
|
||||
|
||||
t.Run("config file with invalid content", func(t *testing.T) {
|
||||
err := os.WriteFile(themeConfigFilePath, []byte("name: [invalid"), 0600)
|
||||
assert.NoError(t, err)
|
||||
cfg := LoadThemeConfig()
|
||||
assert.Equal(t, defaultTheme().Info.Title, cfg.Info.Title)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,15 +20,14 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestApplicationList_ToTableBody(t *testing.T) {
|
||||
appList := &ApplicationList{{"Name", "Namespace", "Phase", "", "", "", "CreateTime"}}
|
||||
assert.Equal(t, appList.ToTableBody(), [][]string{{"Name", "Namespace", "Phase", "", "", "", "CreateTime"}})
|
||||
}
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/common"
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
)
|
||||
|
||||
var _ = Describe("test Application", func() {
|
||||
ctx := context.Background()
|
||||
|
|
@ -66,3 +65,164 @@ var _ = Describe("test Application", func() {
|
|||
Expect(len(topology)).To(Equal(4))
|
||||
})
|
||||
})
|
||||
|
||||
func TestApplicationList_ToTableBody(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
list ApplicationList
|
||||
expected [][]string
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
list: ApplicationList{},
|
||||
expected: make([][]string, 0),
|
||||
},
|
||||
{
|
||||
name: "single item list",
|
||||
list: ApplicationList{
|
||||
{name: "app1", namespace: "ns1", phase: "running", workflowMode: "DAG", workflow: "1/1", service: "1/1", createTime: "now"},
|
||||
},
|
||||
expected: [][]string{
|
||||
{"app1", "ns1", "running", "DAG", "1/1", "1/1", "now"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple item list",
|
||||
list: ApplicationList{
|
||||
{name: "app1", namespace: "ns1", phase: "running", workflowMode: "DAG", workflow: "1/1", service: "1/1", createTime: "now"},
|
||||
{name: "app2", namespace: "ns2", phase: "failed", workflowMode: "StepByStep", workflow: "0/1", service: "0/1", createTime: "then"},
|
||||
},
|
||||
expected: [][]string{
|
||||
{"app1", "ns1", "running", "DAG", "1/1", "1/1", "now"},
|
||||
{"app2", "ns2", "failed", "StepByStep", "0/1", "0/1", "then"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
result := tc.list.ToTableBody()
|
||||
if len(tc.expected) == 0 {
|
||||
assert.Empty(t, result)
|
||||
} else {
|
||||
assert.Equal(t, tc.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceNum(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
app v1beta1.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "no services",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Services: []common.ApplicationComponentStatus{}}},
|
||||
expected: "0/0",
|
||||
},
|
||||
{
|
||||
name: "one healthy, one unhealthy",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Services: []common.ApplicationComponentStatus{
|
||||
{Healthy: true},
|
||||
{Healthy: false},
|
||||
}}},
|
||||
expected: "1/2",
|
||||
},
|
||||
{
|
||||
name: "all healthy",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Services: []common.ApplicationComponentStatus{
|
||||
{Healthy: true},
|
||||
{Healthy: true},
|
||||
}}},
|
||||
expected: "2/2",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, serviceNum(tc.app))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowMode(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
app v1beta1.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "workflow is nil",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: nil}},
|
||||
expected: Unknown,
|
||||
},
|
||||
{
|
||||
name: "workflow mode is DAG",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: &common.WorkflowStatus{Mode: "DAG"}}},
|
||||
expected: "DAG",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, workflowMode(tc.app))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWorkflowStepNum(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
app v1beta1.Application
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "workflow is nil",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: nil}},
|
||||
expected: "N/A",
|
||||
},
|
||||
{
|
||||
name: "empty workflow steps",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{},
|
||||
}}},
|
||||
expected: "0/0",
|
||||
},
|
||||
{
|
||||
name: "all steps succeeded",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded}},
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded}},
|
||||
},
|
||||
}}},
|
||||
expected: "2/2",
|
||||
},
|
||||
{
|
||||
name: "some steps succeeded, some failed/running",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseSucceeded}},
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseFailed}},
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseRunning}},
|
||||
},
|
||||
}}},
|
||||
expected: "1/3",
|
||||
},
|
||||
{
|
||||
name: "all steps failed/running",
|
||||
app: v1beta1.Application{Status: common.AppStatus{Workflow: &common.WorkflowStatus{
|
||||
Steps: []workflowv1alpha1.WorkflowStepStatus{
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseFailed}},
|
||||
{StepStatus: workflowv1alpha1.StepStatus{Phase: workflowv1alpha1.WorkflowStepPhaseRunning}},
|
||||
},
|
||||
}}},
|
||||
expected: "0/2",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, workflowStepNum(tc.app))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,19 +24,43 @@ import (
|
|||
)
|
||||
|
||||
func TestTimeFormat(t *testing.T) {
|
||||
t1, err1 := time.ParseDuration("1.5h")
|
||||
assert.NoError(t, err1)
|
||||
assert.Equal(t, TimeFormat(t1), "1h30m0s")
|
||||
t2, err2 := time.ParseDuration("25h")
|
||||
assert.NoError(t, err2)
|
||||
assert.Equal(t, TimeFormat(t2), "1d1h0m0s")
|
||||
t3, err3 := time.ParseDuration("0.1h")
|
||||
assert.NoError(t, err3)
|
||||
assert.Equal(t, TimeFormat(t3), "6m0s")
|
||||
t4, err4 := time.ParseDuration("0.001h")
|
||||
assert.NoError(t, err4)
|
||||
assert.Equal(t, TimeFormat(t4), "3s")
|
||||
t5, err5 := time.ParseDuration("0.00001h")
|
||||
assert.NoError(t, err5)
|
||||
assert.Equal(t, TimeFormat(t5), "36ms")
|
||||
testCases := []struct {
|
||||
name string
|
||||
in string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "1.5h",
|
||||
in: "1.5h",
|
||||
expected: "1h30m0s",
|
||||
},
|
||||
{
|
||||
name: "25h",
|
||||
in: "25h",
|
||||
expected: "1d1h0m0s",
|
||||
},
|
||||
{
|
||||
name: "0.1h",
|
||||
in: "0.1h",
|
||||
expected: "6m0s",
|
||||
},
|
||||
{
|
||||
name: "0.001h",
|
||||
in: "0.001h",
|
||||
expected: "3s",
|
||||
},
|
||||
{
|
||||
name: "0.00001h",
|
||||
in: "0.00001h",
|
||||
expected: "36ms",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
d, err := time.ParseDuration(tc.in)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expected, TimeFormat(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,16 @@ package cuegen
|
|||
import (
|
||||
"bytes"
|
||||
goast "go/ast"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
cueast "cuelang.org/go/cue/ast"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -88,3 +93,200 @@ func TestConvertNullable(t *testing.T) {
|
|||
|
||||
assert.Equal(t, got.String(), string(want))
|
||||
}
|
||||
|
||||
func TestMakeComment(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in *goast.CommentGroup
|
||||
out []string
|
||||
}{
|
||||
{
|
||||
name: "nil comment",
|
||||
in: nil,
|
||||
out: nil,
|
||||
},
|
||||
{
|
||||
name: "empty comment",
|
||||
in: &goast.CommentGroup{},
|
||||
out: nil,
|
||||
},
|
||||
{
|
||||
name: "line comment",
|
||||
in: &goast.CommentGroup{
|
||||
List: []*goast.Comment{
|
||||
{Text: "// hello"},
|
||||
{Text: "// world"},
|
||||
},
|
||||
},
|
||||
out: []string{"// hello", "// world"},
|
||||
},
|
||||
{
|
||||
name: "block comment",
|
||||
in: &goast.CommentGroup{
|
||||
List: []*goast.Comment{
|
||||
{Text: "/* hello world */"},
|
||||
},
|
||||
},
|
||||
out: []string{"// hello world "},
|
||||
},
|
||||
{
|
||||
name: "multiline block comment",
|
||||
in: &goast.CommentGroup{
|
||||
List: []*goast.Comment{
|
||||
{Text: `/*
|
||||
* hello
|
||||
* world
|
||||
*/`},
|
||||
},
|
||||
},
|
||||
out: []string{"// * hello", "// * world", "//"},
|
||||
},
|
||||
{
|
||||
name: "multiline block comment with no space",
|
||||
in: &goast.CommentGroup{
|
||||
List: []*goast.Comment{
|
||||
{Text: `/*
|
||||
hello
|
||||
world
|
||||
*/`},
|
||||
},
|
||||
},
|
||||
out: []string{"// hello", "// world", "//"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cg := makeComment(tc.in)
|
||||
if cg == nil {
|
||||
assert.Nil(t, tc.out)
|
||||
return
|
||||
}
|
||||
var comments []string
|
||||
for _, c := range cg.List {
|
||||
comments = append(comments, c.Text)
|
||||
}
|
||||
assert.Equal(t, tc.out, comments)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func typeFromSource(t *testing.T, src string) types.Type {
|
||||
fset := token.NewFileSet()
|
||||
fullSrc := "package p\n\n" + src
|
||||
f, err := parser.ParseFile(fset, "src.go", fullSrc, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
conf := types.Config{Importer: importer.Default()}
|
||||
pkg, err := conf.Check("p", fset, []*goast.File{f}, nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
obj := pkg.Scope().Lookup("T")
|
||||
assert.NotNil(t, obj, "type T not found in source")
|
||||
return obj.Type()
|
||||
}
|
||||
|
||||
func TestSupportedType(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
src string
|
||||
shouldError bool
|
||||
errorContains string
|
||||
}{
|
||||
{name: "string", src: "type T string", shouldError: false},
|
||||
{name: "pointer", src: "type T *string", shouldError: false},
|
||||
{name: "slice", src: "type T []int", shouldError: false},
|
||||
{name: "map", src: "type T map[string]bool", shouldError: false},
|
||||
{name: "struct", src: "type T struct{ F string }", shouldError: false},
|
||||
{name: "interface", src: "type T interface{}", shouldError: false},
|
||||
{name: "recursive pointer", src: "type T *T", shouldError: true, errorContains: "recursive type"},
|
||||
{name: "recursive struct field", src: "type T struct{ F *T }", shouldError: true, errorContains: "recursive type"},
|
||||
{name: "map with non-string key", src: "type T map[int]string", shouldError: true, errorContains: "unsupported map key type"},
|
||||
{name: "map with struct key", src: `type U struct{}
|
||||
type T map[U]string`, shouldError: true, errorContains: "unsupported map key type"}}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
typ := typeFromSource(t, tc.src)
|
||||
err := supportedType(nil, typ)
|
||||
|
||||
if tc.shouldError {
|
||||
assert.Error(t, err)
|
||||
if tc.errorContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errorContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnumField(t *testing.T) {
|
||||
// Create a dummy generator. The actual fields of Generator are not used by enumField
|
||||
// except for g.opts.types, which is empty here.
|
||||
g := &Generator{}
|
||||
|
||||
defVal1 := "val1"
|
||||
def1 := "1"
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
typSrc string
|
||||
opts *tagOptions
|
||||
expectedErr bool
|
||||
expectedCue cueast.Expr
|
||||
}{
|
||||
{
|
||||
name: "string enum",
|
||||
typSrc: "type T string",
|
||||
opts: &tagOptions{Enum: []string{"val1", "val2"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "int enum",
|
||||
typSrc: "type T int",
|
||||
opts: &tagOptions{Enum: []string{"1", "2"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "string enum with default",
|
||||
typSrc: "type T string",
|
||||
opts: &tagOptions{Enum: []string{"val1", "val2"}, Default: &defVal1},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "int enum with default",
|
||||
typSrc: "type T int",
|
||||
opts: &tagOptions{Enum: []string{"1", "2"}, Default: &def1},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "unsupported type for enum",
|
||||
typSrc: "type T struct{}",
|
||||
opts: &tagOptions{Enum: []string{"val1"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid enum value for int type",
|
||||
typSrc: "type T int",
|
||||
opts: &tagOptions{Enum: []string{"not_an_int"}},
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
typ := typeFromSource(t, tc.typSrc)
|
||||
expr, err := g.enumField(typ, tc.opts)
|
||||
|
||||
if tc.expectedErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, expr)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedCue, expr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,12 +18,21 @@ package cuegen
|
|||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// errorWriter is an io.Writer that always returns an error.
|
||||
type errorWriter struct{}
|
||||
|
||||
func (ew *errorWriter) Write(p []byte) (n int, err error) {
|
||||
return 0, assert.AnError
|
||||
}
|
||||
|
||||
// testGenerator is a helper function to create a valid Generator for tests.
|
||||
func testGenerator(t *testing.T) *Generator {
|
||||
g, err := NewGenerator("testdata/valid.go")
|
||||
require.NoError(t, err)
|
||||
|
|
@ -34,16 +43,47 @@ func testGenerator(t *testing.T) *Generator {
|
|||
}
|
||||
|
||||
func TestNewGenerator(t *testing.T) {
|
||||
g := testGenerator(t)
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid package",
|
||||
path: "testdata/valid.go",
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "non-existent package",
|
||||
path: "testdata/non_existent.go",
|
||||
expectedErr: true,
|
||||
errContains: "could not load Go packages",
|
||||
},
|
||||
}
|
||||
|
||||
assert.NotNil(t, g.pkg)
|
||||
assert.NotNil(t, g.types)
|
||||
assert.Equal(t, g.opts.types, newDefaultOptions().types)
|
||||
assert.Equal(t, g.opts.nullable, newDefaultOptions().nullable)
|
||||
// assert can't compare function
|
||||
assert.True(t, g.opts.typeFilter(nil))
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
g, err := NewGenerator(tc.path)
|
||||
|
||||
assert.Greater(t, len(g.types), 0)
|
||||
if tc.expectedErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, g)
|
||||
if tc.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, g)
|
||||
assert.NotNil(t, g.pkg)
|
||||
assert.NotNil(t, g.types)
|
||||
assert.Equal(t, g.opts.types, newDefaultOptions().types)
|
||||
assert.Equal(t, g.opts.nullable, newDefaultOptions().nullable)
|
||||
assert.True(t, g.opts.typeFilter(nil))
|
||||
assert.Greater(t, len(g.types), 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorPackage(t *testing.T) {
|
||||
|
|
@ -55,41 +95,180 @@ func TestGeneratorPackage(t *testing.T) {
|
|||
func TestGeneratorGenerate(t *testing.T) {
|
||||
g := testGenerator(t)
|
||||
|
||||
decls, err := g.Generate(WithTypes(map[string]Type{
|
||||
"foo": TypeAny,
|
||||
"bar": TypeAny,
|
||||
}), nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, decls)
|
||||
cases := []struct {
|
||||
name string
|
||||
opts []Option
|
||||
expectedErr bool
|
||||
expectedLen int // Expected number of Decls
|
||||
}{
|
||||
{
|
||||
name: "no options",
|
||||
opts: nil,
|
||||
expectedErr: false,
|
||||
expectedLen: 26,
|
||||
},
|
||||
{
|
||||
name: "with types option",
|
||||
opts: []Option{WithTypes(map[string]Type{
|
||||
"foo": TypeAny,
|
||||
"bar": TypeAny,
|
||||
})},
|
||||
expectedErr: false,
|
||||
expectedLen: 26,
|
||||
},
|
||||
}
|
||||
|
||||
decls, err = g.Generate()
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, decls)
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
decls, err := g.Generate(tc.opts...)
|
||||
|
||||
if tc.expectedErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, decls)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, decls)
|
||||
assert.Len(t, decls, tc.expectedLen)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeneratorFormat(t *testing.T) {
|
||||
g := testGenerator(t)
|
||||
|
||||
decls, err := g.Generate()
|
||||
assert.NoError(t, err)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, decls)
|
||||
|
||||
assert.NoError(t, g.Format(io.Discard, decls))
|
||||
assert.NoError(t, g.Format(io.Discard, []Decl{nil, nil}))
|
||||
assert.Error(t, g.Format(nil, decls))
|
||||
assert.Error(t, g.Format(io.Discard, nil))
|
||||
assert.Error(t, g.Format(io.Discard, []Decl{}))
|
||||
cases := []struct {
|
||||
name string
|
||||
writer io.Writer
|
||||
decls []Decl
|
||||
expectedErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid format",
|
||||
writer: io.Discard,
|
||||
decls: decls,
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "nil writer",
|
||||
writer: nil,
|
||||
decls: decls,
|
||||
expectedErr: true,
|
||||
errContains: "nil writer",
|
||||
},
|
||||
{
|
||||
name: "empty decls",
|
||||
writer: io.Discard,
|
||||
decls: []Decl{},
|
||||
expectedErr: true,
|
||||
errContains: "invalid decls",
|
||||
},
|
||||
{
|
||||
name: "writer error",
|
||||
writer: &errorWriter{},
|
||||
decls: decls,
|
||||
expectedErr: true,
|
||||
errContains: assert.AnError.Error(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := g.Format(tc.writer, tc.decls)
|
||||
|
||||
if tc.expectedErr {
|
||||
assert.Error(t, err)
|
||||
if tc.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPackage(t *testing.T) {
|
||||
pkg, err := loadPackage("testdata/valid.go")
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "valid package",
|
||||
path: "testdata/valid.go",
|
||||
expectedErr: false,
|
||||
},
|
||||
{
|
||||
name: "non-existent package",
|
||||
path: "testdata/non_existent.go",
|
||||
expectedErr: true,
|
||||
errContains: "could not load Go packages",
|
||||
},
|
||||
{
|
||||
name: "package with syntax error",
|
||||
path: "testdata/invalid_syntax.go",
|
||||
expectedErr: true,
|
||||
errContains: "could not load Go packages",
|
||||
},
|
||||
}
|
||||
|
||||
// Create a temporary file with syntax errors for "package with syntax error" case
|
||||
invalidGoContent := `package main
|
||||
|
||||
func main { // Missing parentheses
|
||||
fmt.Println("Hello")
|
||||
}`
|
||||
err := os.WriteFile("testdata/invalid_syntax.go", []byte(invalidGoContent), 0644)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, pkg)
|
||||
require.Len(t, pkg.Errors, 0)
|
||||
t.Cleanup(func() {
|
||||
os.Remove("testdata/invalid_syntax.go")
|
||||
})
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pkg, err := loadPackage(tc.path)
|
||||
|
||||
if tc.expectedErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, pkg)
|
||||
if tc.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tc.errContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, pkg)
|
||||
assert.Len(t, pkg.Errors, 0)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTypeInfo(t *testing.T) {
|
||||
pkg, err := loadPackage("testdata/valid.go")
|
||||
require.NoError(t, err)
|
||||
cases := []struct {
|
||||
name string
|
||||
path string
|
||||
expectedLen int
|
||||
}{
|
||||
{
|
||||
name: "valid package",
|
||||
path: "testdata/valid.go",
|
||||
expectedLen: 40,
|
||||
}}
|
||||
|
||||
require.Greater(t, len(getTypeInfo(pkg)), 0)
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
pkg, err := loadPackage(tc.path)
|
||||
require.NoError(t, err)
|
||||
|
||||
typeInfo := getTypeInfo(pkg)
|
||||
assert.Len(t, typeInfo, tc.expectedLen)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import (
|
|||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cueast "cuelang.org/go/cue/ast"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
|
@ -30,51 +31,124 @@ import (
|
|||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
got := bytes.Buffer{}
|
||||
err := Generate(Options{
|
||||
File: "testdata/valid.go",
|
||||
Writer: &got,
|
||||
Types: map[string]cuegen.Type{
|
||||
"*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured": cuegen.TypeEllipsis,
|
||||
"*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.UnstructuredList": cuegen.TypeEllipsis,
|
||||
},
|
||||
Nullable: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected, err := os.ReadFile("testdata/valid.cue")
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(expected), got.String())
|
||||
}
|
||||
|
||||
func TestGenerateInvalid(t *testing.T) {
|
||||
if err := filepath.Walk("testdata/invalid", func(path string, info os.FileInfo, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
got := bytes.Buffer{}
|
||||
err := Generate(Options{
|
||||
File: path,
|
||||
File: "testdata/valid.go",
|
||||
Writer: &got,
|
||||
Types: map[string]cuegen.Type{
|
||||
"*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.Unstructured": cuegen.TypeEllipsis,
|
||||
"*k8s.io/apimachinery/pkg/apis/meta/v1/unstructured.UnstructuredList": cuegen.TypeEllipsis,
|
||||
},
|
||||
Nullable: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
expected, err := os.ReadFile("testdata/valid.cue")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, string(expected), got.String())
|
||||
})
|
||||
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
if err := filepath.Walk("testdata/invalid", func(path string, info os.FileInfo, e error) error {
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := Generate(Options{
|
||||
File: path,
|
||||
Writer: io.Discard,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty file", func(t *testing.T) {
|
||||
err := Generate(Options{
|
||||
File: "",
|
||||
Writer: io.Discard,
|
||||
})
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Error(err)
|
||||
func TestExtractProviders(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
g, err := cuegen.NewGenerator("testdata/valid.go")
|
||||
require.NoError(t, err)
|
||||
providers, err := extractProviders(g.Package())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, providers, 4)
|
||||
assert.Equal(t, `"apply"`, providers[0].name)
|
||||
assert.Equal(t, "ResourceParams", providers[0].params)
|
||||
assert.Equal(t, "ResourceReturns", providers[0].returns)
|
||||
assert.Equal(t, "Apply", providers[0].do)
|
||||
})
|
||||
|
||||
t.Run("no provider map", func(t *testing.T) {
|
||||
g, err := cuegen.NewGenerator("testdata/invalid/no_provider_map.go")
|
||||
require.NoError(t, err)
|
||||
_, err = extractProviders(g.Package())
|
||||
assert.EqualError(t, err, "no provider function map found like 'map[string]github.com/kubevela/pkg/cue/cuex/runtime.ProviderFn'")
|
||||
})
|
||||
}
|
||||
|
||||
func TestModifyDecls(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
decls []cuegen.Decl
|
||||
providers []provider
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "valid",
|
||||
decls: []cuegen.Decl{
|
||||
&cuegen.Struct{CommonFields: cuegen.CommonFields{Name: "Params", Expr: &cueast.StructLit{Elts: []cueast.Decl{&cueast.Field{Label: cueast.NewIdent("p"), Value: cueast.NewIdent("string")}}}}},
|
||||
&cuegen.Struct{CommonFields: cuegen.CommonFields{Name: "Returns", Expr: &cueast.StructLit{Elts: []cueast.Decl{&cueast.Field{Label: cueast.NewIdent("r"), Value: cueast.NewIdent("string")}}}}},
|
||||
},
|
||||
providers: []provider{
|
||||
{name: `"my-do"`, params: "Params", returns: "Returns", do: "MyDo"},
|
||||
},
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "no providers",
|
||||
decls: []cuegen.Decl{},
|
||||
providers: []provider{},
|
||||
wantLen: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
newDecls, err := modifyDecls("my-provider", tt.decls, tt.providers)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newDecls, tt.wantLen)
|
||||
if tt.wantLen > 0 {
|
||||
s, ok := newDecls[0].(*cuegen.Struct)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "#MyDo", s.Name)
|
||||
require.Len(t, s.Expr.(*cueast.StructLit).Elts, 4)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateEmptyError(t *testing.T) {
|
||||
err := Generate(Options{
|
||||
File: "",
|
||||
Writer: io.Discard,
|
||||
func TestRecoverAssert(t *testing.T) {
|
||||
t.Run("panic recovery", func(t *testing.T) {
|
||||
var err error
|
||||
func() {
|
||||
defer recoverAssert(&err, "test panic")
|
||||
panic("panic message")
|
||||
}()
|
||||
assert.EqualError(t, err, "panic message: panic: test panic")
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
Copyright 2021 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package docgen
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/require"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
func TestGenerateCUETemplateProperties(t *testing.T) {
|
||||
// Read componentDef for a valid capability
|
||||
componentDefYAML, err := os.ReadFile("testdata/componentDef.yaml")
|
||||
require.NoError(t, err)
|
||||
var componentDef v1beta1.ComponentDefinition
|
||||
err = yaml.Unmarshal(componentDefYAML, &componentDef)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Define a struct to unmarshal the raw extension
|
||||
type extensionSpec struct {
|
||||
Template string `json:"template"`
|
||||
}
|
||||
var extSpec extensionSpec
|
||||
err = yaml.Unmarshal(componentDef.Spec.Extension.Raw, &extSpec)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Define test cases
|
||||
testCases := []struct {
|
||||
name string
|
||||
capability *types.Capability
|
||||
expectedTables int
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid component definition",
|
||||
capability: &types.Capability{
|
||||
Name: "test-component",
|
||||
CueTemplate: extSpec.Template,
|
||||
},
|
||||
expectedTables: 2,
|
||||
expectErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid cue template",
|
||||
capability: &types.Capability{
|
||||
Name: "invalid-cue",
|
||||
CueTemplate: `parameter: { image: }`,
|
||||
},
|
||||
expectErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ref := &ConsoleReference{}
|
||||
doc, console, err := ref.GenerateCUETemplateProperties(tc.capability)
|
||||
|
||||
if tc.expectErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, doc)
|
||||
require.Len(t, console, tc.expectedTables)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateTerraformCapabilityProperties(t *testing.T) {
|
||||
ref := &ConsoleReference{}
|
||||
type args struct {
|
||||
cap types.Capability
|
||||
}
|
||||
|
||||
type want struct {
|
||||
tableName1 string
|
||||
tableName2 string
|
||||
errMsg string
|
||||
}
|
||||
testcases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"normal": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
TerraformConfiguration: `
|
||||
resource "alicloud_oss_bucket" "bucket-acl" {
|
||||
bucket = var.bucket
|
||||
acl = var.acl
|
||||
}
|
||||
|
||||
output "BUCKET_NAME" {
|
||||
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
|
||||
}
|
||||
|
||||
variable "bucket" {
|
||||
description = "OSS bucket name"
|
||||
default = "vela-website"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "acl" {
|
||||
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
|
||||
default = "private"
|
||||
type = string
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "",
|
||||
tableName1: "",
|
||||
tableName2: "#### writeConnectionSecretToRef",
|
||||
},
|
||||
},
|
||||
"configuration is in git remote": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
Name: "ecs",
|
||||
TerraformConfiguration: "https://github.com/wonderflow/terraform-alicloud-ecs-instance.git",
|
||||
ConfigurationType: "remote",
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "",
|
||||
tableName1: "",
|
||||
tableName2: "#### writeConnectionSecretToRef",
|
||||
},
|
||||
},
|
||||
"configuration is not valid": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
TerraformConfiguration: `abc`,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to generate capability properties: :1,1-4: Argument or block definition required; An " +
|
||||
"argument or block definition is required here. To set an argument, use the equals sign \"=\" to " +
|
||||
"introduce the argument value.",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
consoleRef, err := ref.GenerateTerraformCapabilityProperties(tc.args.cap)
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
if diff := cmp.Diff(tc.want.errMsg, errMsg); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want error, +got error:\n%s\n", name, diff)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(2, len(consoleRef)); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.tableName1, consoleRef[0].TableName); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.tableName2, consoleRef[1].TableName); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package docgen
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"github.com/oam-dev/kubevela/apis/types"
|
||||
)
|
||||
|
||||
func TestParseCapabilityFromUnstructured(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
obj unstructured.Unstructured
|
||||
wantCap types.Capability
|
||||
wantErr bool
|
||||
wantErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "trait definition",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "TraitDefinition",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-trait",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"appliesToWorkloads": []interface{}{"webservice", "worker"},
|
||||
"schematic": map[string]interface{}{
|
||||
"cue": map[string]interface{}{
|
||||
"template": "parameter: {}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCap: types.Capability{
|
||||
Name: "my-trait",
|
||||
Type: types.TypeTrait,
|
||||
AppliesTo: []string{"webservice", "worker"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "component definition",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "ComponentDefinition",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-comp",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"workload": map[string]interface{}{
|
||||
"type": "worker",
|
||||
},
|
||||
"schematic": map[string]interface{}{
|
||||
"cue": map[string]interface{}{
|
||||
"template": "parameter: {}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, wantCap: types.Capability{
|
||||
Name: "my-comp",
|
||||
Type: types.TypeComponentDefinition,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "policy definition",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "PolicyDefinition",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-policy",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"schematic": map[string]interface{}{
|
||||
"cue": map[string]interface{}{
|
||||
"template": "parameter: {}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCap: types.Capability{
|
||||
Name: "my-policy",
|
||||
Type: types.TypePolicy,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "workflow step definition",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "WorkflowStepDefinition",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-step",
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"schematic": map[string]interface{}{
|
||||
"cue": map[string]interface{}{
|
||||
"template": "parameter: {}",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantCap: types.Capability{
|
||||
Name: "my-step",
|
||||
Type: types.TypeWorkflowStep,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "unknown kind",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "UnknownKind",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
wantErrMsg: "unknown definition Type UnknownKind",
|
||||
},
|
||||
{
|
||||
name: "malformed spec",
|
||||
obj: unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "core.oam.dev/v1beta1",
|
||||
"kind": "TraitDefinition",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "my-trait",
|
||||
},
|
||||
"spec": "this-should-be-a-map",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// The mapper is nil for these cases as they don't rely on it.
|
||||
// A separate test would be needed for the mapper-dependent path.
|
||||
cap, err := ParseCapabilityFromUnstructured(nil, tc.obj)
|
||||
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
if tc.wantErrMsg != "" {
|
||||
require.Contains(t, err.Error(), tc.wantErrMsg)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.wantCap.Name, cap.Name)
|
||||
require.Equal(t, tc.wantCap.Type, cap.Type)
|
||||
require.Equal(t, tc.wantCap.AppliesTo, cap.AppliesTo)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -21,34 +21,83 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintf(w, `{"Outputs":{"Chinese":"输出"}}`)
|
||||
}))
|
||||
defer svr.Close()
|
||||
time.Sleep(time.Millisecond)
|
||||
assert.Equal(t, En.Language(), Language("English"))
|
||||
assert.Equal(t, En.Get("nihaoha"), "nihaoha")
|
||||
assert.Equal(t, En.Get("AlibabaCloud"), "Alibaba Cloud")
|
||||
var ni *I18n
|
||||
assert.Equal(t, ni.Get("AlibabaCloud"), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud."), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud。"), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud。 "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud 。 "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud \n "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get(" A\n "), "A")
|
||||
assert.Equal(t, ni.Get(" \n "), "")
|
||||
func TestI18n(t *testing.T) {
|
||||
t.Run("English defaults", func(t *testing.T) {
|
||||
assert.Equal(t, En.Language(), Language("English"))
|
||||
assert.Equal(t, En.Get("nihaoha"), "nihaoha")
|
||||
assert.Equal(t, En.Get("AlibabaCloud"), "Alibaba Cloud")
|
||||
})
|
||||
|
||||
assert.Equal(t, Zh.Language(), Language("Chinese"))
|
||||
assert.Equal(t, Zh.Get("nihaoha"), "nihaoha")
|
||||
assert.Equal(t, Zh.Get("AlibabaCloud"), "阿里云")
|
||||
t.Run("Chinese defaults", func(t *testing.T) {
|
||||
assert.Equal(t, Zh.Language(), Language("Chinese"))
|
||||
assert.Equal(t, Zh.Get("nihaoha"), "nihaoha")
|
||||
assert.Equal(t, Zh.Get("AlibabaCloud"), "阿里云")
|
||||
})
|
||||
|
||||
LoadI18nData(svr.URL)
|
||||
assert.Equal(t, Zh.Get("Outputs"), "输出")
|
||||
t.Run("nil receiver", func(t *testing.T) {
|
||||
var ni *I18n
|
||||
assert.Equal(t, ni.Get("AlibabaCloud"), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud."), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud。"), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud。 "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud 。 "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get("AlibabaCloud \n "), "Alibaba Cloud")
|
||||
assert.Equal(t, ni.Get(" A\n "), "A")
|
||||
assert.Equal(t, ni.Get(" \n "), "")
|
||||
})
|
||||
|
||||
t.Run("Get with fallback logic", func(t *testing.T) {
|
||||
// Test suffix trimming
|
||||
assert.Equal(t, "Description", En.Get("Description."))
|
||||
assert.Equal(t, "描述", Zh.Get("描述。"))
|
||||
|
||||
// Test lowercase fallback (Note: this reveals a bug, as it doesn't find the capitalized key)
|
||||
assert.Equal(t, "description", En.Get("description"))
|
||||
assert.Equal(t, "description", Zh.Get("description"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadI18nData(t *testing.T) {
|
||||
t.Run("Load external data", func(t *testing.T) {
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprintf(w, `{"Outputs":{"Chinese":"输出"}}`)
|
||||
}))
|
||||
defer svr.Close()
|
||||
LoadI18nData(svr.URL)
|
||||
assert.Equal(t, "输出", Zh.Get("Outputs"))
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadI18nDataErrors(t *testing.T) {
|
||||
t.Run("http error", func(t *testing.T) {
|
||||
// Check that a non-existent key is not translated before the call
|
||||
assert.Equal(t, "TestKey", Zh.Get("TestKey"))
|
||||
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer svr.Close()
|
||||
LoadI18nData(svr.URL)
|
||||
|
||||
// Assert that the key is still not translated
|
||||
assert.Equal(t, "TestKey", Zh.Get("TestKey"))
|
||||
})
|
||||
|
||||
t.Run("malformed json", func(t *testing.T) {
|
||||
// Check that another non-existent key is not translated
|
||||
assert.Equal(t, "AnotherKey", Zh.Get("AnotherKey"))
|
||||
|
||||
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = fmt.Fprint(w, `this-is-not-json`)
|
||||
}))
|
||||
defer svr.Close()
|
||||
LoadI18nData(svr.URL)
|
||||
|
||||
// Assert that the key is still not translated
|
||||
assert.Equal(t, "AnotherKey", Zh.Get("AnotherKey"))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
Copyright 2022 The KubeVela Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package docgen
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGenerateConsoleDocument(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
title string
|
||||
schema *openapi3.Schema
|
||||
wantOutput string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty schema",
|
||||
title: "Test",
|
||||
schema: &openapi3.Schema{},
|
||||
wantOutput: "",
|
||||
},
|
||||
{
|
||||
name: "simple schema",
|
||||
title: "",
|
||||
schema: &openapi3.Schema{
|
||||
Properties: map[string]*openapi3.SchemaRef{
|
||||
"name": {
|
||||
Value: &openapi3.Schema{
|
||||
Title: "name",
|
||||
Description: "The name of the resource.",
|
||||
Type: &openapi3.Types{openapi3.TypeString},
|
||||
},
|
||||
},
|
||||
"port": {
|
||||
Value: &openapi3.Schema{
|
||||
Title: "port",
|
||||
Description: "The port to expose.",
|
||||
Type: &openapi3.Types{openapi3.TypeInteger},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutput: `
|
||||
+------+---------+---------------------------+----------+---------+---------+
|
||||
| NAME | TYPE | DESCRIPTION | REQUIRED | OPTIONS | DEFAULT |
|
||||
+------+---------+---------------------------+----------+---------+---------+
|
||||
| name | string | The name of the resource. | false | | |
|
||||
| port | integer | The port to expose. | false | | |
|
||||
+------+---------+---------------------------+----------+---------+---------+
|
||||
`,
|
||||
},
|
||||
{
|
||||
name: "nested schema",
|
||||
title: "parent",
|
||||
schema: &openapi3.Schema{
|
||||
Required: []string{"child"},
|
||||
Properties: map[string]*openapi3.SchemaRef{
|
||||
"child": {
|
||||
Value: &openapi3.Schema{
|
||||
Title: "child",
|
||||
Type: &openapi3.Types{openapi3.TypeObject},
|
||||
Properties: map[string]*openapi3.SchemaRef{
|
||||
"leaf": {
|
||||
Value: &openapi3.Schema{
|
||||
Title: "leaf",
|
||||
Type: &openapi3.Types{openapi3.TypeString},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantOutput: `parent
|
||||
+----------------+--------+-------------+----------+---------+---------+
|
||||
| NAME | TYPE | DESCRIPTION | REQUIRED | OPTIONS | DEFAULT |
|
||||
+----------------+--------+-------------+----------+---------+---------+
|
||||
| (parent).child | object | | true | | |
|
||||
+----------------+--------+-------------+----------+---------+---------+
|
||||
parent.child
|
||||
+---------------------+--------+-------------+----------+---------+---------+
|
||||
| NAME | TYPE | DESCRIPTION | REQUIRED | OPTIONS | DEFAULT |
|
||||
+---------------------+--------+-------------+----------+---------+---------+
|
||||
| (parent.child).leaf | string | | false | | |
|
||||
+---------------------+--------+-------------+----------+---------+---------+
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
doc, err := GenerateConsoleDocument(tc.title, tc.schema)
|
||||
if tc.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
// Trim whitespace for consistent comparison and sort lines to avoid flakiness
|
||||
expectedLines := strings.Split(strings.TrimSpace(tc.wantOutput), "\n")
|
||||
actualLines := strings.Split(strings.TrimSpace(doc), "\n")
|
||||
sort.Strings(expectedLines)
|
||||
sort.Strings(actualLines)
|
||||
require.Equal(t, expectedLines, actualLines)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/crossplane/crossplane-runtime/pkg/test"
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
|
@ -262,103 +261,6 @@ func TestWalkParameterSchema(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateTerraformCapabilityProperties(t *testing.T) {
|
||||
ref := &ConsoleReference{}
|
||||
type args struct {
|
||||
cap types.Capability
|
||||
}
|
||||
|
||||
type want struct {
|
||||
tableName1 string
|
||||
tableName2 string
|
||||
errMsg string
|
||||
}
|
||||
testcases := map[string]struct {
|
||||
args args
|
||||
want want
|
||||
}{
|
||||
"normal": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
TerraformConfiguration: `
|
||||
resource "alicloud_oss_bucket" "bucket-acl" {
|
||||
bucket = var.bucket
|
||||
acl = var.acl
|
||||
}
|
||||
|
||||
output "BUCKET_NAME" {
|
||||
value = "${alicloud_oss_bucket.bucket-acl.bucket}.${alicloud_oss_bucket.bucket-acl.extranet_endpoint}"
|
||||
}
|
||||
|
||||
variable "bucket" {
|
||||
description = "OSS bucket name"
|
||||
default = "vela-website"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "acl" {
|
||||
description = "OSS bucket ACL, supported 'private', 'public-read', 'public-read-write'"
|
||||
default = "private"
|
||||
type = string
|
||||
}
|
||||
`,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "",
|
||||
tableName1: "",
|
||||
tableName2: "#### writeConnectionSecretToRef",
|
||||
},
|
||||
},
|
||||
"configuration is in git remote": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
Name: "ecs",
|
||||
TerraformConfiguration: "https://github.com/wonderflow/terraform-alicloud-ecs-instance.git",
|
||||
ConfigurationType: "remote",
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "",
|
||||
tableName1: "",
|
||||
tableName2: "#### writeConnectionSecretToRef",
|
||||
},
|
||||
},
|
||||
"configuration is not valid": {
|
||||
args: args{
|
||||
cap: types.Capability{
|
||||
TerraformConfiguration: `abc`,
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
errMsg: "failed to generate capability properties: :1,1-4: Argument or block definition required; An " +
|
||||
"argument or block definition is required here. To set an argument, use the equals sign \"=\" to " +
|
||||
"introduce the argument value.",
|
||||
},
|
||||
},
|
||||
}
|
||||
for name, tc := range testcases {
|
||||
consoleRef, err := ref.GenerateTerraformCapabilityProperties(tc.args.cap)
|
||||
var errMsg string
|
||||
if err != nil {
|
||||
errMsg = err.Error()
|
||||
if diff := cmp.Diff(tc.want.errMsg, errMsg, test.EquateErrors()); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want error, +got error:\n%s\n", name, diff)
|
||||
}
|
||||
} else {
|
||||
if diff := cmp.Diff(len(consoleRef), 2); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.tableName1, consoleRef[0].TableName); diff != "" {
|
||||
t.Errorf("\n%s\nGenerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
if diff := cmp.Diff(tc.want.tableName2, consoleRef[1].TableName); diff != "" {
|
||||
t.Errorf("\n%s\nGexnerateTerraformCapabilityProperties(...): -want, +got:\n%s\n", name, diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPrepareTerraformOutputs(t *testing.T) {
|
||||
type args struct {
|
||||
tableName string
|
||||
|
|
|
|||
Loading…
Reference in New Issue