diff --git a/pkg/render/template.go b/pkg/render/template.go index cf553de3..6958e2f3 100644 --- a/pkg/render/template.go +++ b/pkg/render/template.go @@ -16,279 +16,349 @@ limitations under the License. package render import ( - "bytes" - "context" - "crypto/md5" - "crypto/rand" - "crypto/sha256" - _ "embed" - "encoding/base64" - "encoding/hex" - "encoding/json" - "fmt" - "io" - mathrand "math/rand" - "net/url" - "strings" - "text/template" - "time" - - "github.com/linuxsuren/api-testing/pkg/version" - - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "errors" - - "github.com/Masterminds/sprig/v3" - "github.com/linuxsuren/api-testing/pkg/secret" - "github.com/linuxsuren/api-testing/pkg/util" - "gopkg.in/yaml.v3" + "bytes" + "context" + "crypto/md5" + "crypto/rand" + "crypto/sha256" + _ "embed" + "encoding/base64" + "encoding/hex" + "encoding/json" + "fmt" + "io" + "math" + mathrand "math/rand" + "net/url" + "strconv" + "strings" + "text/template" + "time" + + "github.com/linuxsuren/api-testing/pkg/version" + + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" + + "github.com/Masterminds/sprig/v3" + "github.com/linuxsuren/api-testing/pkg/secret" + "github.com/linuxsuren/api-testing/pkg/util" + "gopkg.in/yaml.v3" ) var secretGetter secret.SecretGetter // SetSecretGetter set the secret getter func SetSecretGetter(getter secret.SecretGetter) { - if getter == nil { - getter = &nonSecretGetter{ - err: fmt.Errorf("no secret server"), - } - } - secretGetter = getter + if getter == nil { + getter = &nonSecretGetter{ + err: fmt.Errorf("no secret server"), + } + } + secretGetter = getter } // Render render then return the result func Render(name, text string, ctx interface{}) (result string, err error) { - var data []byte - if data, err = RenderAsBytes(name, text, ctx); err == nil { - result = string(data) - } - return + var data []byte + if data, err = RenderAsBytes(name, text, ctx); err == nil { + result = string(data) + } + return } func RenderAsBytes(name, text string, ctx interface{}) (data []byte, err error) { - var tpl *template.Template - if tpl, err = template.New(name). - Funcs(FuncMap()). - Parse(text); err == nil { - buf := new(bytes.Buffer) - if err = tpl.Execute(buf, ctx); err == nil { - data = buf.Bytes() - } - } - return + var tpl *template.Template + if tpl, err = template.New(name). + Funcs(FuncMap()). + Parse(text); err == nil { + buf := new(bytes.Buffer) + if err = tpl.Execute(buf, ctx); err == nil { + data = buf.Bytes() + } + } + return } func RenderAsReader(name, text string, ctx interface{}) (reader io.Reader, err error) { - var data []byte - if data, err = RenderAsBytes(name, text, ctx); err == nil { - reader = bytes.NewReader(data) - } - return + var data []byte + if data, err = RenderAsBytes(name, text, ctx); err == nil { + reader = bytes.NewReader(data) + } + return } // FuncMap returns all the supported functions func FuncMap() template.FuncMap { - funcs := sprig.FuncMap() - for _, item := range GetAdvancedFuncs() { - if item.FuncName == "" || item.Func == nil { - continue - } - funcs[item.FuncName] = item.Func - } - funcs["rasEncryptWithPublicKey"] = rasEncryptWithPublicKey - funcs["randImage"] = generateRandomImage - funcs["randPdf"] = generateRandomPdf - funcs["randZip"] = generateRandomZip - funcs["readFile"] = readFile - return funcs + funcs := sprig.FuncMap() + for _, item := range GetAdvancedFuncs() { + if item.FuncName == "" || item.Func == nil { + continue + } + funcs[item.FuncName] = item.Func + } + funcs["rasEncryptWithPublicKey"] = rasEncryptWithPublicKey + funcs["randImage"] = generateRandomImage + funcs["randPdf"] = generateRandomPdf + funcs["randZip"] = generateRandomZip + funcs["readFile"] = readFile + return funcs } // FuncUsage returns the usage of target template function func FuncUsage(funcName string) (usage string) { - usageMap := make(map[string]string) - if err := yaml.Unmarshal(templateUsage, usageMap); err == nil { - usage = usageMap[funcName] - } - return + usageMap := make(map[string]string) + if err := yaml.Unmarshal(templateUsage, usageMap); err == nil { + usage = usageMap[funcName] + } + return } // RenderThenPrint renders the template then prints the result func RenderThenPrint(name, text string, ctx interface{}, w io.Writer) (err error) { - var report string - if report, err = Render(name, text, ctx); err == nil { - _, err = fmt.Fprint(w, report) - } - return + var report string + if report, err = Render(name, text, ctx); err == nil { + _, err = fmt.Fprint(w, report) + } + return } var advancedFuncs = []AdvancedFunc{{ - FuncName: "generateJSONString", - Func: generateJSONString, - GoDogExper: `^生成对象,字段包含 (.*)$`, - Generator: generateJSONObject, + FuncName: "generateJSONString", + Func: generateJSONString, + GoDogExper: `^生成对象,字段包含 (.*)$`, + Generator: generateJSONObject, }, { - FuncName: "randomKubernetesName", - Func: func() string { - return util.String(8) - }, - GoDogExper: `^动态k8s名称(.*)$`, - Generator: func(ctx context.Context, fields string) (err error) { - writeWithContext(ctx, `{{randomKubernetesName}}`) - return - }, + FuncName: "randomKubernetesName", + Func: func() string { + return util.String(8) + }, + GoDogExper: `^动态k8s名称(.*)$`, + Generator: func(ctx context.Context, fields string) (err error) { + writeWithContext(ctx, `{{randomKubernetesName}}`) + return + }, }, { - GoDogExper: `^生成随机字符串,长度 (.*)$`, - Generator: func(ctx context.Context, fields string) (err error) { - writeWithContext(ctx, `{{randAlpha `+fields+`}}`) - return - }, + GoDogExper: `^生成随机字符串,长度 (.*)$`, + Generator: func(ctx context.Context, fields string) (err error) { + writeWithContext(ctx, `{{randAlpha `+fields+`}}`) + return + }, }, { - FuncName: "secretValue", - Func: func(name string) string { - val, err := secretGetter.GetSecret(name) - if err == nil { - return val.Value - } - return err.Error() - }, + FuncName: "secretValue", + Func: func(name string) string { + val, err := secretGetter.GetSecret(name) + if err == nil { + return val.Value + } + return err.Error() + }, }, { - FuncName: "md5", - Func: func(text string) string { - hash := md5.Sum([]byte(text)) - return hex.EncodeToString(hash[:]) - }, + FuncName: "md5", + Func: func(text string) string { + hash := md5.Sum([]byte(text)) + return hex.EncodeToString(hash[:]) + }, }, { - FuncName: "base64", - Func: func(text string) string { - return base64.StdEncoding.EncodeToString([]byte(text)) - }, + FuncName: "base64", + Func: func(text string) string { + return base64.StdEncoding.EncodeToString([]byte(text)) + }, }, { - FuncName: "base64Decode", - Func: func(text string) string { - result, err := base64.StdEncoding.DecodeString(text) - if err == nil { - return string(result) - } else { - return err.Error() - } - }, + FuncName: "base64Decode", + Func: func(text string) string { + result, err := base64.StdEncoding.DecodeString(text) + if err == nil { + return string(result) + } else { + return err.Error() + } + }, }, { - FuncName: "sha256sumBytes", - Func: func(data []byte) string { - h := sha256.New() - h.Write(data) - return hex.EncodeToString(h.Sum(nil)) - }, + FuncName: "sha256sumBytes", + Func: func(data []byte) string { + h := sha256.New() + h.Write(data) + return hex.EncodeToString(h.Sum(nil)) + }, }, { - FuncName: "randFloat", - Func: func(from float64, to float64) float64 { - return mathrand.Float64()*(to-from) + from - }, + FuncName: "randNorm", + Func: func(mean, stdDev float64) float64 { + return randNorm(mean, stdDev) + }, }, { - FuncName: "randEnum", - Func: func(items ...string) string { - return randItem(items...) - }, + FuncName: "randNormInt", + Func: func(mean, stdDev float64) int { + return int(randNorm(mean, stdDev)) + }, }, { - FuncName: "randEnumByStr", - Func: func(item string) string { - return randItem(strings.Split(item, ",")...) - }, + FuncName: "randLogNorm", + Func: func(mean, stdDev float64) float64 { + return randLogNorm(mean, stdDev) + }, }, { - FuncName: "randEnumByJSON", - Func: func(item string) interface{} { - var items []interface{} - if err := json.Unmarshal([]byte(item), &items); err == nil { - return randItem(items...) - } - return &WeightEnum{} - }, + FuncName: "randLogNormInt", + Func: func(mean, stdDev float64) float64 { + return randLogNorm(mean, stdDev) + }, }, { - FuncName: "weightObject", - Func: func(weight int, object interface{}) WeightEnum { - return WeightEnum{ - Weight: weight, - Object: object, - } - }, + FuncName: "randFloat", + Func: func(from float64, to float64) float64 { + return mathrand.Float64()*(to-from) + from + }, }, { - FuncName: "randWeightEnum", - Func: func(items ...WeightEnum) interface{} { - var newItems []interface{} - for _, item := range items { - for j := 0; j < item.Weight; j++ { - newItems = append(newItems, item.Object) - } - } - return newItems[mathrand.Intn(len(newItems))] - }, + FuncName: "randEnum", + Func: func(items ...string) string { + return randItem(items...) + }, }, { - FuncName: "randEmail", - Func: func() string { - return fmt.Sprintf("%s@%s.com", util.String(3), util.String(3)) - }, + FuncName: "randEnumByStr", + Func: func(item string) string { + return randItem(strings.Split(item, ",")...) + }, }, { - FuncName: "uptimeDate", - Func: func() time.Time { - return uptime - }, + FuncName: "randEnumByJSON", + Func: func(item string) interface{} { + var items []interface{} + if err := json.Unmarshal([]byte(item), &items); err == nil { + return randItem(items...) + } + return &WeightEnum{} + }, }, { - FuncName: "uptime", - Func: func() string { - return time.Since(uptime).String() - }, + FuncName: "weightObject", + Func: func(weight int, object interface{}) WeightEnum { + return WeightEnum{ + Weight: weight, + Object: object, + } + }, }, { - FuncName: "uptimeSeconds", - Func: func() float64 { - return time.Since(uptime).Seconds() - }, + FuncName: "randWeightEnum", + Func: func(items ...WeightEnum) interface{} { + var newItems []interface{} + for _, item := range items { + for j := 0; j < item.Weight; j++ { + newItems = append(newItems, item.Object) + } + } + return newItems[mathrand.Intn(len(newItems))] + }, }, { - FuncName: "urlEncode", - Func: func(text string) string { - return url.QueryEscape(text) - }, + FuncName: "randEmail", + Func: func() string { + return fmt.Sprintf("%s@%s.com", util.String(3), util.String(3)) + }, }, { - FuncName: "urlDecode", - Func: func(text string) (result string) { - result, _ = url.QueryUnescape(text) - return - }, + FuncName: "uptimeDate", + Func: func() time.Time { + return uptime + }, +}, { + FuncName: "uptime", + Func: func() string { + return time.Since(uptime).String() + }, +}, { + FuncName: "uptimeSeconds", + Func: func() float64 { + return time.Since(uptime).Seconds() + }, +}, { + FuncName: "urlEncode", + Func: func(text string) string { + return url.QueryEscape(text) + }, +}, { + FuncName: "urlDecode", + Func: func(text string) (result string) { + result, _ = url.QueryUnescape(text) + return + }, +}, { + FuncName: "arange", + Func: func(from, to, step int) []int { + var result []int + for i := from; i <= to; i += step { + result = append(result, i) + } + return result + }, +}, { + FuncName: "arangeIP", + Func: func(from string, count int) []string { + result := []string{from} + last := from + for i := 1; i < count; i++ { + segs := strings.Split(last, ".") + last = ipV4Increase(segs, 3) + if last == result[len(result)-1] { + return result + } + result = append(result, last) + } + return result + }, }} +func ipV4Increase(segs []string, index int) string { + if index > 3 || index < 0 || index >= len(segs) { + return strings.Join(segs, ".") + } + + if num, err := strconv.Atoi(segs[index]); err != nil { + return strings.Join(segs, ".") + } else if num >= 255 { + segs[index] = "1" + return ipV4Increase(segs, index-1) + } else { + segs[index] = strconv.Itoa(num + 1) + return strings.Join(segs, ".") + } +} + +func randNorm(mean, stdDev float64) float64 { + return mean + stdDev*mathrand.NormFloat64() +} + +func randLogNorm(mean, stdDev float64) float64 { + return math.Exp(randNorm(mean, stdDev)) +} + func randItem[T any](items ...T) T { - return items[mathrand.Intn(len(items))] + return items[mathrand.Intn(len(items))] } // WeightEnum is a weight enum type WeightEnum struct { - Weight int - Object interface{} + Weight int + Object interface{} } var uptime = time.Now() // GetAdvancedFuncs returns all the advanced functions func GetAdvancedFuncs() []AdvancedFunc { - return advancedFuncs + return advancedFuncs } func GetEngineVersion() (ver string) { - ver, _ = version.GetModVersion("github.com/Masterminds/sprig", "") - return + ver, _ = version.GetModVersion("github.com/Masterminds/sprig", "") + return } func generateJSONString(fields []string) (result string) { - data := make(map[string]string) - for _, item := range fields { - data[item] = "random" - } - - if json, err := json.Marshal(data); err == nil { - result = string(json) - } - return + data := make(map[string]string) + for _, item := range fields { + data[item] = "random" + } + + if json, err := json.Marshal(data); err == nil { + result = string(json) + } + return } type ContextKey string @@ -298,56 +368,56 @@ var ContextBufferKey ContextKey = "ContextBufferKey" // generateJSONObject generates a json object // For instance: {{generateJSONString "hello" "world"}} func generateJSONObject(ctx context.Context, fields string) (err error) { - items := strings.Split(fields, ",") - funcExp := "{{generateJSONString" - for _, item := range items { - funcExp += " \"" + strings.TrimSpace(item) + "\"" - } - funcExp += "}}" - - writeWithContext(ctx, funcExp) - return + items := strings.Split(fields, ",") + funcExp := "{{generateJSONString" + for _, item := range items { + funcExp += " \"" + strings.TrimSpace(item) + "\"" + } + funcExp += "}}" + + writeWithContext(ctx, funcExp) + return } func writeWithContext(ctx context.Context, text string) { - buf := ctx.Value(ContextBufferKey) - writer, ok := buf.(io.Writer) - if ok && writer != nil { - _, _ = writer.Write([]byte(text)) - } + buf := ctx.Value(ContextBufferKey) + writer, ok := buf.(io.Writer) + if ok && writer != nil { + _, _ = writer.Write([]byte(text)) + } } // AdvancedFunc represents an advanced function type AdvancedFunc struct { - FuncName string - Func interface{} - GoDogExper string - Generator func(ctx context.Context, fields string) (err error) + FuncName string + Func interface{} + GoDogExper string + Generator func(ctx context.Context, fields string) (err error) } // rasEncryptWithPublicKey encrypts the given content with the provided public key func rasEncryptWithPublicKey(content, key string) (string, error) { - block, _ := pem.Decode([]byte(key)) - if block == nil { - return "", errors.New("failed to parse PEM block containing the public key") - } - - pub, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return "", fmt.Errorf("failed to parse DER encoded public key: %s", err) - } - - rsaPub, ok := pub.(*rsa.PublicKey) - if !ok { - return "", errors.New("key type is not RSA") - } - - encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPub, []byte(content)) - if err != nil { - return "", fmt.Errorf("failed to encrypt with RSA public key: %s", err) - } - - return base64.StdEncoding.EncodeToString(encryptedData), nil + block, _ := pem.Decode([]byte(key)) + if block == nil { + return "", errors.New("failed to parse PEM block containing the public key") + } + + pub, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return "", fmt.Errorf("failed to parse DER encoded public key: %s", err) + } + + rsaPub, ok := pub.(*rsa.PublicKey) + if !ok { + return "", errors.New("key type is not RSA") + } + + encryptedData, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPub, []byte(content)) + if err != nil { + return "", fmt.Errorf("failed to encrypt with RSA public key: %s", err) + } + + return base64.StdEncoding.EncodeToString(encryptedData), nil } //go:embed data/templateUsage.yaml diff --git a/pkg/render/template_test.go b/pkg/render/template_test.go index 2f4b9340..04fd2f55 100644 --- a/pkg/render/template_test.go +++ b/pkg/render/template_test.go @@ -16,335 +16,383 @@ limitations under the License. package render import ( - "bytes" - "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/pem" - "io" - "testing" + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "io" + "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/assert" ) func TestRender(t *testing.T) { - tests := []struct { - name string - text string - ctx interface{} - expect string - verify func(*testing.T, string) - }{{ - name: "default", - text: `{{default "hello" .Bar}}`, - ctx: nil, - expect: "hello", - }, { - name: "trim", - text: `{{trim " hello "}}`, - ctx: "", - expect: "hello", - }, { - name: "randomKubernetesName", - text: `{{randomKubernetesName}}`, - verify: func(t *testing.T, s string) { - assert.Equal(t, 8, len(s)) - }, - }, { - name: "md5", - text: `{{md5 "linuxsuren"}}`, - expect: "b559b80ae1ba1c292d9b3265f265e76a", - }, { - name: "base64", - text: `{{base64 "linuxsuren"}}`, - expect: "bGludXhzdXJlbg==", - }, { - name: "base64Decode", - text: `{{base64Decode "bGludXhzdXJlbg=="}}`, - expect: "linuxsuren", - }, { - name: "base64Decode with error", - text: `{{base64Decode "error"}}`, - expect: "illegal base64 data at input byte 4", - }, { - name: "complex", - text: `{{(index .items 0).name}}?a=a&key={{randomKubernetesName}}`, - ctx: map[string]interface{}{ - "items": []interface{}{map[string]string{ - "name": "one", - }, map[string]string{ - "name": "two", - }}, - }, - verify: func(t *testing.T, s string) { - assert.Equal(t, 20, len(s), s) - }, - }, { - name: "randFloat", - text: `{{randFloat 1 2}}`, - verify: func(t *testing.T, s string) { - assert.NotEmpty(t, s) - }, - }, { - name: "randEnum", - text: `{{randEnum "a" "b" "c"}}`, - verify: func(t *testing.T, s string) { - assert.Contains(t, []string{"a", "b", "c"}, s) - }, - }, { - name: "randEnumByStr", - text: `{{randEnumByStr "a,b,c"}}`, - verify: func(t *testing.T, s string) { - assert.Contains(t, []string{"a", "b", "c"}, s) - }, - }, { - name: "randEnumByJSON", - text: `{{(randEnumByJSON "[{\"key\":\"a\"},{\"key\":\"b\"},{\"key\":\"c\"}]").key}}`, - verify: func(t *testing.T, s string) { - assert.Contains(t, []string{"a", "b", "c"}, s) - }, - }, { - name: "randWeightEnum", - text: `{{randWeightEnum (weightObject 1 "a") (weightObject 2 "b") (weightObject 3 "c")}}`, - verify: func(t *testing.T, s string) { - assert.Contains(t, []string{"a", "b", "c"}, s) - }, - }, { - name: "randEmail", - text: `{{randEmail}}`, - verify: func(t *testing.T, s string) { - assert.Contains(t, s, "@") - assert.Contains(t, s, ".com") - }, - }, { - name: "sha256 bytes", - text: `{{sha256sumBytes .data}}`, - ctx: map[string]interface{}{ - "data": []byte("hello"), - }, - verify: func(t *testing.T, s string) { - assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", s) - }, - }, { - name: "uptimeDate", - text: `{{uptimeDate}}`, - verify: func(t *testing.T, s string) { - assert.NotEmpty(t, s) - }, - }, { - name: "uptime", - text: `{{uptime}}`, - verify: func(t *testing.T, s string) { - assert.NotEmpty(t, s) - }, - }, { - name: "uptimeSeconds", - text: `{{uptimeSeconds}}`, - verify: func(t *testing.T, s string) { - assert.NotEmpty(t, s) - }, - }, { - name: "url encode", - text: `{{urlEncode "hello world"}}`, - verify: func(t *testing.T, s string) { - assert.Equal(t, "hello+world", s) - }, - }, { - name: "url decode", - text: `{{urlDecode "hello+world"}}`, - verify: func(t *testing.T, s string) { - assert.Equal(t, "hello world", s) - }, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result, err := Render(tt.name, tt.text, tt.ctx) - assert.Nil(t, err, err) - if tt.expect != "" { - assert.Equal(t, tt.expect, result) - } - if tt.verify != nil { - tt.verify(t, result) - } - }) - } + tests := []struct { + name string + text string + ctx interface{} + expect string + verify func(*testing.T, string) + }{{ + name: "default", + text: `{{default "hello" .Bar}}`, + ctx: nil, + expect: "hello", + }, { + name: "trim", + text: `{{trim " hello "}}`, + ctx: "", + expect: "hello", + }, { + name: "randomKubernetesName", + text: `{{randomKubernetesName}}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, 8, len(s)) + }, + }, { + name: "md5", + text: `{{md5 "linuxsuren"}}`, + expect: "b559b80ae1ba1c292d9b3265f265e76a", + }, { + name: "base64", + text: `{{base64 "linuxsuren"}}`, + expect: "bGludXhzdXJlbg==", + }, { + name: "base64Decode", + text: `{{base64Decode "bGludXhzdXJlbg=="}}`, + expect: "linuxsuren", + }, { + name: "base64Decode with error", + text: `{{base64Decode "error"}}`, + expect: "illegal base64 data at input byte 4", + }, { + name: "complex", + text: `{{(index .items 0).name}}?a=a&key={{randomKubernetesName}}`, + ctx: map[string]interface{}{ + "items": []interface{}{map[string]string{ + "name": "one", + }, map[string]string{ + "name": "two", + }}, + }, + verify: func(t *testing.T, s string) { + assert.Equal(t, 20, len(s), s) + }, + }, { + name: "randFloat", + text: `{{randFloat 1 2}}`, + verify: func(t *testing.T, s string) { + assert.NotEmpty(t, s) + }, + }, { + name: "randEnum", + text: `{{randEnum "a" "b" "c"}}`, + verify: func(t *testing.T, s string) { + assert.Contains(t, []string{"a", "b", "c"}, s) + }, + }, { + name: "randEnumByStr", + text: `{{randEnumByStr "a,b,c"}}`, + verify: func(t *testing.T, s string) { + assert.Contains(t, []string{"a", "b", "c"}, s) + }, + }, { + name: "randEnumByJSON", + text: `{{(randEnumByJSON "[{\"key\":\"a\"},{\"key\":\"b\"},{\"key\":\"c\"}]").key}}`, + verify: func(t *testing.T, s string) { + assert.Contains(t, []string{"a", "b", "c"}, s) + }, + }, { + name: "randWeightEnum", + text: `{{randWeightEnum (weightObject 1 "a") (weightObject 2 "b") (weightObject 3 "c")}}`, + verify: func(t *testing.T, s string) { + assert.Contains(t, []string{"a", "b", "c"}, s) + }, + }, { + name: "randEmail", + text: `{{randEmail}}`, + verify: func(t *testing.T, s string) { + assert.Contains(t, s, "@") + assert.Contains(t, s, ".com") + }, + }, { + name: "sha256 bytes", + text: `{{sha256sumBytes .data}}`, + ctx: map[string]interface{}{ + "data": []byte("hello"), + }, + verify: func(t *testing.T, s string) { + assert.Equal(t, "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824", s) + }, + }, { + name: "randNorm", + text: `{{randNorm 50 2}}-{{randNormInt 50 2}}-{{randLogNorm 50 2}}-{{randLogNormInt 50 2}}`, + verify: func(t *testing.T, s string) { + assert.NotEmpty(t, s) + }, + }, { + name: "uptimeDate", + text: `{{uptimeDate}}`, + verify: func(t *testing.T, s string) { + assert.NotEmpty(t, s) + }, + }, { + name: "uptime", + text: `{{uptime}}`, + verify: func(t *testing.T, s string) { + assert.NotEmpty(t, s) + }, + }, { + name: "uptimeSeconds", + text: `{{uptimeSeconds}}`, + verify: func(t *testing.T, s string) { + assert.NotEmpty(t, s) + }, + }, { + name: "url encode", + text: `{{urlEncode "hello world"}}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "hello+world", s) + }, + }, { + name: "url decode", + text: `{{urlDecode "hello+world"}}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "hello world", s) + }, + }, { + name: "arange", + text: `{{ range $i := (arange 1 10 1)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "12345678910", s) + }, + }, { + name: "arangeIP, simple", + text: `{{ range $i := (arangeIP "192.168.1.1" 3)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "192.168.1.1192.168.1.2192.168.1.3", s) + }, + }, { + name: "arangeIP, across 3 and 4", + text: `{{ range $i := (arangeIP "192.168.1.254" 4)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "192.168.1.254192.168.1.255192.168.2.1192.168.2.2", s) + }, + }, { + name: "arangeIP, across 2 and 3", + text: `{{ range $i := (arangeIP "192.168.255.254" 4)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "192.168.255.254192.168.255.255192.169.1.1192.169.1.2", s) + }, + }, { + name: "arangeIP, invalid start IP", + text: `{{ range $i := (arangeIP "192.168.255" 4)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "192.168.255", s) + }, + }, { + name: "arangeIP, invalid start IP, not number", + text: `{{ range $i := (arangeIP "a.b.c.d" 4)}}{{$i}}{{ end }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "a.b.c.d", s) + }, + }, { + name: "arangeIP, a lot", + text: `{{ len (arangeIP "192.168.1.1" 865) }}`, + verify: func(t *testing.T, s string) { + assert.Equal(t, "865", s) + }, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := Render(tt.name, tt.text, tt.ctx) + assert.Nil(t, err, err) + if tt.expect != "" { + assert.Equal(t, tt.expect, result) + } + if tt.verify != nil { + tt.verify(t, result) + } + }) + } } func TestRenderThenPrint(t *testing.T) { - tests := []struct { - name string - tplText string - ctx interface{} - buf *bytes.Buffer - expect string - }{{ - name: "simple", - tplText: `{{max 1 2 3}}`, - ctx: nil, - buf: new(bytes.Buffer), - expect: "3", - }, { - name: "with a map as context", - tplText: `{{.name}}`, - ctx: map[string]string{"name": "linuxsuren"}, - buf: new(bytes.Buffer), - expect: "linuxsuren", - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := RenderThenPrint(tt.name, tt.tplText, tt.ctx, tt.buf) - assert.NoError(t, err) - assert.Equal(t, tt.expect, tt.buf.String()) - }) - } + tests := []struct { + name string + tplText string + ctx interface{} + buf *bytes.Buffer + expect string + }{{ + name: "simple", + tplText: `{{max 1 2 3}}`, + ctx: nil, + buf: new(bytes.Buffer), + expect: "3", + }, { + name: "with a map as context", + tplText: `{{.name}}`, + ctx: map[string]string{"name": "linuxsuren"}, + buf: new(bytes.Buffer), + expect: "linuxsuren", + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := RenderThenPrint(tt.name, tt.tplText, tt.ctx, tt.buf) + assert.NoError(t, err) + assert.Equal(t, tt.expect, tt.buf.String()) + }) + } } func TestFuncGenerator(t *testing.T) { - tests := []struct { - name string - funcName string - fields string - expect string - }{{ - name: "randomKubernetesName", - funcName: "randomKubernetesName", - expect: `{{randomKubernetesName}}`, - }, { - name: "generateJSONString", - funcName: "generateJSONString", - fields: "name, age", - expect: `{{generateJSONString "name" "age"}}`, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - funcs := GetAdvancedFuncs() - for _, f := range funcs { - if f.FuncName == tt.funcName { - buf := new(bytes.Buffer) - ctx := context.Background() - ctx = context.WithValue(ctx, ContextBufferKey, buf) - err := f.Generator(ctx, tt.fields) - assert.NoError(t, err) - assert.Equal(t, tt.expect, buf.String()) - } - } - }) - } + tests := []struct { + name string + funcName string + fields string + expect string + }{{ + name: "randomKubernetesName", + funcName: "randomKubernetesName", + expect: `{{randomKubernetesName}}`, + }, { + name: "generateJSONString", + funcName: "generateJSONString", + fields: "name, age", + expect: `{{generateJSONString "name" "age"}}`, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + funcs := GetAdvancedFuncs() + for _, f := range funcs { + if f.FuncName == tt.funcName { + buf := new(bytes.Buffer) + ctx := context.Background() + ctx = context.WithValue(ctx, ContextBufferKey, buf) + err := f.Generator(ctx, tt.fields) + assert.NoError(t, err) + assert.Equal(t, tt.expect, buf.String()) + } + } + }) + } } func TestGoDogGenerator(t *testing.T) { - tests := []struct { - name string - goDogExper string - fields string - expect string - }{{ - name: "randomKubernetesName", - goDogExper: `^生成随机字符串,长度 (.*)$`, - fields: `3`, - expect: `{{randAlpha 3}}`, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - funcs := GetAdvancedFuncs() - for _, f := range funcs { - if f.GoDogExper == tt.goDogExper { - buf := new(bytes.Buffer) - ctx := context.Background() - ctx = context.WithValue(ctx, ContextBufferKey, buf) - err := f.Generator(ctx, tt.fields) - assert.NoError(t, err) - assert.Equal(t, tt.expect, buf.String()) - } - } - }) - } + tests := []struct { + name string + goDogExper string + fields string + expect string + }{{ + name: "randomKubernetesName", + goDogExper: `^生成随机字符串,长度 (.*)$`, + fields: `3`, + expect: `{{randAlpha 3}}`, + }} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + funcs := GetAdvancedFuncs() + for _, f := range funcs { + if f.GoDogExper == tt.goDogExper { + buf := new(bytes.Buffer) + ctx := context.Background() + ctx = context.WithValue(ctx, ContextBufferKey, buf) + err := f.Generator(ctx, tt.fields) + assert.NoError(t, err) + assert.Equal(t, tt.expect, buf.String()) + } + } + }) + } } func TestGenerateJSONString(t *testing.T) { - result := generateJSONString([]string{"name", "age"}) - assert.Equal(t, `{"age":"random","name":"random"}`, result) + result := generateJSONString([]string{"name", "age"}) + assert.Equal(t, `{"age":"random","name":"random"}`, result) } func TestSecret(t *testing.T) { - SetSecretGetter(nil) - result, err := Render("", `{{secretValue "pass"}}`, nil) - assert.NoError(t, err) - assert.Equal(t, "no secret server", result) + SetSecretGetter(nil) + result, err := Render("", `{{secretValue "pass"}}`, nil) + assert.NoError(t, err) + assert.Equal(t, "no secret server", result) - expected := "password" - SetSecretGetter(&nonSecretGetter{ - value: expected, - }) - result, err = Render("", `{{secretValue "pass"}}`, nil) - assert.NoError(t, err) - assert.Equal(t, expected, result) + expected := "password" + SetSecretGetter(&nonSecretGetter{ + value: expected, + }) + result, err = Render("", `{{secretValue "pass"}}`, nil) + assert.NoError(t, err) + assert.Equal(t, expected, result) - t.Run("render as reader", func(t *testing.T) { - reader, err := RenderAsReader("render as reader", "hello", nil) - assert.NoError(t, err) - assert.NotNil(t, reader) + t.Run("render as reader", func(t *testing.T) { + reader, err := RenderAsReader("render as reader", "hello", nil) + assert.NoError(t, err) + assert.NotNil(t, reader) - data, err := io.ReadAll(reader) - assert.NoError(t, err) - assert.Equal(t, "hello", string(data)) - }) + data, err := io.ReadAll(reader) + assert.NoError(t, err) + assert.Equal(t, "hello", string(data)) + }) } func TestRasEncryptWithPublicKey(t *testing.T) { - // Generate a new RSA key pair - privateKey, err := rsa.GenerateKey(rand.Reader, 2048) - if err != nil { - t.Fatalf("Failed to generate private key: %v", err) - } - publicKey := &privateKey.PublicKey + // Generate a new RSA key pair + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("Failed to generate private key: %v", err) + } + publicKey := &privateKey.PublicKey - // Encode the public key to PEM format - pubASN1, err := x509.MarshalPKIXPublicKey(publicKey) - if err != nil { - t.Fatalf("Failed to marshal public key: %v", err) - } - pubBytes := pem.EncodeToMemory(&pem.Block{ - Type: "RSA PUBLIC KEY", - Bytes: pubASN1, - }) + // Encode the public key to PEM format + pubASN1, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + t.Fatalf("Failed to marshal public key: %v", err) + } + pubBytes := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: pubASN1, + }) - // Encrypt a message using the public key - message := "hello world" - encryptedMessage, err := rasEncryptWithPublicKey(message, string(pubBytes)) - if err != nil { - t.Fatalf("Failed to encrypt message: %v", err) - } + // Encrypt a message using the public key + message := "hello world" + encryptedMessage, err := rasEncryptWithPublicKey(message, string(pubBytes)) + if err != nil { + t.Fatalf("Failed to encrypt message: %v", err) + } - // Decrypt the message using the private key - decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage) - if err != nil { - t.Fatalf("Failed to decode message: %v", err) - } - decryptedBytes, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decodedMessage) - if err != nil { - t.Fatalf("Failed to decrypt message: %v", err) - } + // Decrypt the message using the private key + decodedMessage, err := base64.StdEncoding.DecodeString(encryptedMessage) + if err != nil { + t.Fatalf("Failed to decode message: %v", err) + } + decryptedBytes, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, decodedMessage) + if err != nil { + t.Fatalf("Failed to decrypt message: %v", err) + } - // Verify the decrypted message - decryptedMessage := string(decryptedBytes) - if decryptedMessage != message { - t.Fatalf("Decrypted message does not match original. Got: %s, want: %s", decryptedMessage, message) - } + // Verify the decrypted message + decryptedMessage := string(decryptedBytes) + if decryptedMessage != message { + t.Fatalf("Decrypted message does not match original. Got: %s, want: %s", decryptedMessage, message) + } } func TestFuncUsages(t *testing.T) { - funcs := []string{"randImage", "randZip"} + funcs := []string{"randImage", "randZip"} - for _, f := range funcs { - usage := FuncUsage(f) - assert.NotEmpty(t, usage) - } + for _, f := range funcs { + usage := FuncUsage(f) + assert.NotEmpty(t, usage) + } } func TestGetEngineVersion(t *testing.T) { - ver := GetEngineVersion() - assert.Empty(t, ver) + ver := GetEngineVersion() + assert.Empty(t, ver) }