Feat(testing): Enhance Unit Test Coverage for Core Utility Packages (#6929)

* test(cli): enhance unit test coverage for theme and color config

This commit introduces a comprehensive suite of unit tests for the theme and color configuration functions in `references/cli/top/config`.

Key changes include:
- Refactored existing tests in `color_test.go` to use table-driven sub-tests for improved clarity and maintainability.
- Added new test functions to validate color parsing, hex color detection, and default theme creation.
- Implemented tests for theme file lifecycle management, including creation and loading logic.

These additions significantly increase the test coverage and ensure the robustness and correctness of the CLI's theme and color functionality.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* test(cli): refactor and enhance tests for top view models and utils

This commit improves the unit test suite for the CLI's top view functionality by refactoring existing tests and adding new ones to increase coverage.

Key changes include:
- In `application_test.go`, `TestApplicationList_ToTableBody` is refactored to be a table-driven test, and new tests are added for `serviceNum`, `workflowMode`, and `workflowStepNum` helpers.
- In `time_test.go`, `TestTimeFormat` is refactored into a table-driven test for better structure and readability.

These changes align the tests with best practices and improve the overall robustness of the CLI top view's data presentation logic.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* test(cuegen): enhance unit test coverage for CUE generation packages

This commit introduces a comprehensive suite of unit tests and refactors existing tests for the CUE generation packages located in `references/cuegen`.

Key changes include:
- Refactored existing tests in `generator_test.go` and `provider_test.go` to use table-driven sub-tests, improving clarity, maintainability, and coverage of error conditions.
- Added new test functions to `convert_test.go` to validate helper functions for comment generation, type support, and enum field handling.
- Added new tests in `provider_test.go` to cover provider extraction, declaration modification, and panic recovery logic.

These changes significantly increase the test coverage for the `cuegen` libraries, ensuring the correctness and robustness of the CUE code generation functionality.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* test(docgen): add comprehensive unit tests for doc generation

This commit introduces a comprehensive suite of unit tests for the documentation generation package located in `references/docgen`.

Key changes include:
- Added new test files (`console_test.go`, `convert_test.go`, `openapi_test.go`) to cover the core functions for parsing and generating documentation for CUE, Terraform, and OpenAPI schemas.
- Refactored and enhanced `i18n_test.go` to use sub-tests, resolve race conditions, and improve coverage for fallback logic and error handling.
- Ensured all new and existing tests follow best practices, using table-driven tests for clarity and maintainability.

This effort significantly increases the test coverage for the `docgen` package, improving the reliability and robustness of the documentation generation features.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

* test: improve test reliability and conventions

This commit introduces several improvements to the test suite to enhance reliability and adhere to best practices.

- **Fix flaky test in `docgen/openapi_test.go`**:
  The test for `GenerateConsoleDocument` was flaky because it performed an exact string match on table output generated from a map. Since map iteration order is not guaranteed, this could cause spurious failures. The test is now order-insensitive, comparing sorted sets of lines instead.

- **Improve assertions in `docgen/console_test.go`**:
  - Removes an unnecessary `test.EquateErrors()` option, which is not needed for simple string comparisons.
  - Corrects the `cmp.Diff` argument order to the standard `(want, got)` convention for clearer failure messages.
  - Fixes a typo in an error message.

- **Standardize assertions in `cli/top/config/color_test.go`**:
  Swaps `assert.Equal` arguments to the standard `(expected, actual)` convention.

- **Clean up `cuegen/generators/provider/provider_test.go`**:
  Removes a redundant error check.

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>

---------

Signed-off-by: Ashvin Bambhaniya <ashvin.bambhaniya@improwised.com>
This commit is contained in:
AshvinBambhaniya2003
2025-10-31 19:20:30 +05:30
committed by GitHub
parent 44ac92d1ba
commit 24f6718619
11 changed files with 1468 additions and 213 deletions

View File

@@ -18,6 +18,7 @@ package config
import ( import (
"os" "os"
"path/filepath"
"strings" "strings"
"testing" "testing"
@@ -27,15 +28,82 @@ import (
) )
func TestString(t *testing.T) { func TestString(t *testing.T) {
c := Color("red") testCases := map[string]struct {
assert.Equal(t, c.String(), "#ff0000") 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) { func TestColor(t *testing.T) {
c1 := Color("#ff0000") c1 := Color("#ff0000")
assert.Equal(t, c1.Color(), tcell.GetColor("#ff0000")) assert.Equal(t, tcell.GetColor("#ff0000"), c1.Color())
c2 := Color("red") 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) { func TestPersistentThemeConfig(t *testing.T) {
@@ -45,3 +113,101 @@ func TestPersistentThemeConfig(t *testing.T) {
assert.Nil(t, err) assert.Nil(t, err)
assert.True(t, strings.Contains(string(bytes), "foo")) 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)
})
}

View File

@@ -20,15 +20,14 @@ import (
"context" "context"
"testing" "testing"
workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
)
func TestApplicationList_ToTableBody(t *testing.T) { "github.com/oam-dev/kubevela/apis/core.oam.dev/common"
appList := &ApplicationList{{"Name", "Namespace", "Phase", "", "", "", "CreateTime"}} "github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
assert.Equal(t, appList.ToTableBody(), [][]string{{"Name", "Namespace", "Phase", "", "", "", "CreateTime"}}) )
}
var _ = Describe("test Application", func() { var _ = Describe("test Application", func() {
ctx := context.Background() ctx := context.Background()
@@ -66,3 +65,164 @@ var _ = Describe("test Application", func() {
Expect(len(topology)).To(Equal(4)) 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))
})
}
}

View File

@@ -24,19 +24,43 @@ import (
) )
func TestTimeFormat(t *testing.T) { func TestTimeFormat(t *testing.T) {
t1, err1 := time.ParseDuration("1.5h") testCases := []struct {
assert.NoError(t, err1) name string
assert.Equal(t, TimeFormat(t1), "1h30m0s") in string
t2, err2 := time.ParseDuration("25h") expected string
assert.NoError(t, err2) }{
assert.Equal(t, TimeFormat(t2), "1d1h0m0s") {
t3, err3 := time.ParseDuration("0.1h") name: "1.5h",
assert.NoError(t, err3) in: "1.5h",
assert.Equal(t, TimeFormat(t3), "6m0s") expected: "1h30m0s",
t4, err4 := time.ParseDuration("0.001h") },
assert.NoError(t, err4) {
assert.Equal(t, TimeFormat(t4), "3s") name: "25h",
t5, err5 := time.ParseDuration("0.00001h") in: "25h",
assert.NoError(t, err5) expected: "1d1h0m0s",
assert.Equal(t, TimeFormat(t5), "36ms") },
{
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))
})
}
} }

View File

@@ -19,11 +19,16 @@ package cuegen
import ( import (
"bytes" "bytes"
goast "go/ast" goast "go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"testing" "testing"
cueast "cuelang.org/go/cue/ast"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@@ -88,3 +93,200 @@ func TestConvertNullable(t *testing.T) {
assert.Equal(t, got.String(), string(want)) 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)
}
})
}
}

View File

@@ -18,12 +18,21 @@ package cuegen
import ( import (
"io" "io"
"os"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "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 { func testGenerator(t *testing.T) *Generator {
g, err := NewGenerator("testdata/valid.go") g, err := NewGenerator("testdata/valid.go")
require.NoError(t, err) require.NoError(t, err)
@@ -34,16 +43,47 @@ func testGenerator(t *testing.T) *Generator {
} }
func TestNewGenerator(t *testing.T) { 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) for _, tc := range cases {
assert.NotNil(t, g.types) t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, g.opts.types, newDefaultOptions().types) g, err := NewGenerator(tc.path)
assert.Equal(t, g.opts.nullable, newDefaultOptions().nullable)
// assert can't compare function
assert.True(t, g.opts.typeFilter(nil))
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) { func TestGeneratorPackage(t *testing.T) {
@@ -55,41 +95,180 @@ func TestGeneratorPackage(t *testing.T) {
func TestGeneratorGenerate(t *testing.T) { func TestGeneratorGenerate(t *testing.T) {
g := testGenerator(t) g := testGenerator(t)
decls, err := g.Generate(WithTypes(map[string]Type{ cases := []struct {
"foo": TypeAny, name string
"bar": TypeAny, opts []Option
}), nil) expectedErr bool
assert.NoError(t, err) expectedLen int // Expected number of Decls
assert.NotNil(t, 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() for _, tc := range cases {
assert.NoError(t, err) t.Run(tc.name, func(t *testing.T) {
assert.NotNil(t, decls) 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) { func TestGeneratorFormat(t *testing.T) {
g := testGenerator(t) g := testGenerator(t)
decls, err := g.Generate() decls, err := g.Generate()
assert.NoError(t, err) require.NoError(t, err)
require.NotNil(t, decls)
assert.NoError(t, g.Format(io.Discard, decls)) cases := []struct {
assert.NoError(t, g.Format(io.Discard, []Decl{nil, nil})) name string
assert.Error(t, g.Format(nil, decls)) writer io.Writer
assert.Error(t, g.Format(io.Discard, nil)) decls []Decl
assert.Error(t, g.Format(io.Discard, []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) { 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.NoError(t, err)
require.NotNil(t, pkg) t.Cleanup(func() {
require.Len(t, pkg.Errors, 0) 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) { func TestGetTypeInfo(t *testing.T) {
pkg, err := loadPackage("testdata/valid.go") cases := []struct {
require.NoError(t, err) 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)
})
}
} }

View File

@@ -23,6 +23,7 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
cueast "cuelang.org/go/cue/ast"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -30,51 +31,124 @@ import (
) )
func TestGenerate(t *testing.T) { func TestGenerate(t *testing.T) {
got := bytes.Buffer{} t.Run("valid", func(t *testing.T) {
err := Generate(Options{ got := bytes.Buffer{}
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
}
err := Generate(Options{ 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, Writer: io.Discard,
}) })
assert.Error(t, err) assert.Error(t, err)
})
}
return nil func TestExtractProviders(t *testing.T) {
}); err != nil { t.Run("valid", func(t *testing.T) {
t.Error(err) 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) { func TestRecoverAssert(t *testing.T) {
err := Generate(Options{ t.Run("panic recovery", func(t *testing.T) {
File: "", var err error
Writer: io.Discard, func() {
defer recoverAssert(&err, "test panic")
panic("panic message")
}()
assert.EqualError(t, err, "panic message: panic: test panic")
}) })
assert.Error(t, err)
} }

View File

@@ -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)
}
}
})
}
}

View File

@@ -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)
})
}
}

View File

@@ -21,34 +21,83 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestLoad(t *testing.T) { func TestI18n(t *testing.T) {
svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t.Run("English defaults", func(t *testing.T) {
_, _ = fmt.Fprintf(w, `{"Outputs":{"Chinese":"输出"}}`) assert.Equal(t, En.Language(), Language("English"))
})) assert.Equal(t, En.Get("nihaoha"), "nihaoha")
defer svr.Close() assert.Equal(t, En.Get("AlibabaCloud"), "Alibaba Cloud")
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 "), "")
assert.Equal(t, Zh.Language(), Language("Chinese")) t.Run("Chinese defaults", func(t *testing.T) {
assert.Equal(t, Zh.Get("nihaoha"), "nihaoha") assert.Equal(t, Zh.Language(), Language("Chinese"))
assert.Equal(t, Zh.Get("AlibabaCloud"), "阿里云") assert.Equal(t, Zh.Get("nihaoha"), "nihaoha")
assert.Equal(t, Zh.Get("AlibabaCloud"), "阿里云")
})
LoadI18nData(svr.URL) t.Run("nil receiver", func(t *testing.T) {
assert.Equal(t, Zh.Get("Outputs"), "输出") 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"))
})
} }

View File

@@ -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)
})
}
}

View File

@@ -24,7 +24,6 @@ import (
"reflect" "reflect"
"testing" "testing"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/getkin/kin-openapi/openapi3" "github.com/getkin/kin-openapi/openapi3"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert" "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) { func TestPrepareTerraformOutputs(t *testing.T) {
type args struct { type args struct {
tableName string tableName string