fat: add template function randomKubernetesName (#48)
This commit is contained in:
parent
607818fd4b
commit
9df7a6663a
|
@ -63,6 +63,14 @@ The following fields are templated with [sprig](http://masterminds.github.io/spr
|
||||||
* Request Body
|
* Request Body
|
||||||
* Request Header
|
* Request Header
|
||||||
|
|
||||||
|
### Functions
|
||||||
|
|
||||||
|
You could use all the common functions which comes from [sprig](http://masterminds.github.io/sprig/). Besides some specific functions are available:
|
||||||
|
|
||||||
|
| Name | Usage |
|
||||||
|
|---|---|
|
||||||
|
| `randomKubernetesName` | `{{randomKubernetesName}}` to generate Kubernetes resource name randomly, the name will have 8 chars |
|
||||||
|
|
||||||
## Verify against Kubernetes
|
## Verify against Kubernetes
|
||||||
|
|
||||||
It could verify any kinds of Kubernetes resources. Please set the environment variables before using it:
|
It could verify any kinds of Kubernetes resources. Please set the environment variables before using it:
|
||||||
|
|
|
@ -6,12 +6,19 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Masterminds/sprig/v3"
|
"github.com/Masterminds/sprig/v3"
|
||||||
|
"github.com/linuxsuren/api-testing/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Render render then return the result
|
// Render render then return the result
|
||||||
func Render(name, text string, ctx interface{}) (result string, err error) {
|
func Render(name, text string, ctx interface{}) (result string, err error) {
|
||||||
var tpl *template.Template
|
var tpl *template.Template
|
||||||
if tpl, err = template.New(name).Funcs(sprig.FuncMap()).Parse(text); err == nil {
|
if tpl, err = template.New(name).
|
||||||
|
Funcs(sprig.FuncMap()).
|
||||||
|
Funcs(template.FuncMap{
|
||||||
|
"randomKubernetesName": func() string {
|
||||||
|
return util.String(8)
|
||||||
|
},
|
||||||
|
}).Parse(text); err == nil {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
if err = tpl.Execute(buf, ctx); err == nil {
|
if err = tpl.Execute(buf, ctx); err == nil {
|
||||||
result = strings.TrimSpace(buf.String())
|
result = strings.TrimSpace(buf.String())
|
||||||
|
|
|
@ -12,6 +12,7 @@ func TestRender(t *testing.T) {
|
||||||
text string
|
text string
|
||||||
ctx interface{}
|
ctx interface{}
|
||||||
expect string
|
expect string
|
||||||
|
verify func(*testing.T, string)
|
||||||
}{{
|
}{{
|
||||||
name: "default",
|
name: "default",
|
||||||
text: `{{default "hello" .Bar}}`,
|
text: `{{default "hello" .Bar}}`,
|
||||||
|
@ -22,12 +23,23 @@ func TestRender(t *testing.T) {
|
||||||
text: `{{trim " hello "}}`,
|
text: `{{trim " hello "}}`,
|
||||||
ctx: "",
|
ctx: "",
|
||||||
expect: "hello",
|
expect: "hello",
|
||||||
|
}, {
|
||||||
|
name: "randomKubernetesName",
|
||||||
|
text: `{{randomKubernetesName}}`,
|
||||||
|
verify: func(t *testing.T, s string) {
|
||||||
|
assert.Equal(t, 8, len(s))
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result, err := Render(tt.name, tt.text, tt.ctx)
|
result, err := Render(tt.name, tt.text, tt.ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
if tt.expect != "" {
|
||||||
assert.Equal(t, tt.expect, result)
|
assert.Equal(t, tt.expect, result)
|
||||||
|
}
|
||||||
|
if tt.verify != nil {
|
||||||
|
tt.verify(t, result)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,12 +197,12 @@ func (r *simpleTestCaseRunner) RunTestCase(testcase *testing.TestCase, dataConte
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestBody io.Reader
|
if err = testcase.Request.Render(dataContext); err != nil {
|
||||||
if requestBody, err = testcase.Request.GetBody(); err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = testcase.Request.Render(dataContext); err != nil {
|
var requestBody io.Reader
|
||||||
|
if requestBody, err = testcase.Request.GetBody(); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,17 @@ func TestTestCase(t *testing.T) {
|
||||||
fooRequst := atest.Request{
|
fooRequst := atest.Request{
|
||||||
API: urlFoo,
|
API: urlFoo,
|
||||||
}
|
}
|
||||||
|
defaultForm := map[string]string{
|
||||||
|
"key": "value",
|
||||||
|
}
|
||||||
|
defaultPrepare := func() {
|
||||||
|
gock.New(urlLocalhost).
|
||||||
|
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||||
|
}
|
||||||
|
defaultPostPrepare := func() {
|
||||||
|
gock.New(urlLocalhost).
|
||||||
|
Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||||
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -42,9 +53,7 @@ func TestTestCase(t *testing.T) {
|
||||||
testCase: &atest.TestCase{
|
testCase: &atest.TestCase{
|
||||||
Request: atest.Request{
|
Request: atest.Request{
|
||||||
API: urlFoo,
|
API: urlFoo,
|
||||||
Header: map[string]string{
|
Header: defaultForm,
|
||||||
"key": "value",
|
|
||||||
},
|
|
||||||
Body: `{"foo":"bar"}`,
|
Body: `{"foo":"bar"}`,
|
||||||
},
|
},
|
||||||
Expect: atest.Response{
|
Expect: atest.Response{
|
||||||
|
@ -204,10 +213,7 @@ func TestTestCase(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
prepare: func() {
|
prepare: defaultPrepare,
|
||||||
gock.New(urlLocalhost).
|
|
||||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
|
||||||
},
|
|
||||||
verify: func(t *testing.T, output interface{}, err error) {
|
verify: func(t *testing.T, output interface{}, err error) {
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Contains(t, err.Error(), "failed to get field")
|
assert.Contains(t, err.Error(), "failed to get field")
|
||||||
|
@ -225,10 +231,7 @@ func TestTestCase(t *testing.T) {
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// prepare: func() {
|
// prepare: defaultPrepare,
|
||||||
// gock.New(urlLocalhost).
|
|
||||||
// Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
|
||||||
// },
|
|
||||||
// verify: func(t *testing.T, output interface{}, err error) {
|
// verify: func(t *testing.T, output interface{}, err error) {
|
||||||
// if assert.NotNil(t, err) {
|
// if assert.NotNil(t, err) {
|
||||||
// assert.Contains(t, err.Error(), "failed to verify")
|
// assert.Contains(t, err.Error(), "failed to verify")
|
||||||
|
@ -245,10 +248,7 @@ func TestTestCase(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
prepare: func() {
|
prepare: defaultPrepare,
|
||||||
gock.New(urlLocalhost).
|
|
||||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
|
||||||
},
|
|
||||||
verify: func(t *testing.T, output interface{}, err error) {
|
verify: func(t *testing.T, output interface{}, err error) {
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Contains(t, err.Error(), "unknown name println")
|
assert.Contains(t, err.Error(), "unknown name println")
|
||||||
|
@ -263,10 +263,7 @@ func TestTestCase(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
prepare: func() {
|
prepare: defaultPrepare,
|
||||||
gock.New(urlLocalhost).
|
|
||||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
|
||||||
},
|
|
||||||
verify: func(t *testing.T, output interface{}, err error) {
|
verify: func(t *testing.T, output interface{}, err error) {
|
||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
assert.Contains(t, err.Error(), "expected bool, but got int")
|
assert.Contains(t, err.Error(), "expected bool, but got int")
|
||||||
|
@ -303,15 +300,10 @@ func TestTestCase(t *testing.T) {
|
||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
util.ContentType: "multipart/form-data",
|
util.ContentType: "multipart/form-data",
|
||||||
},
|
},
|
||||||
Form: map[string]string{
|
Form: defaultForm,
|
||||||
"key": "value",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
prepare: defaultPostPrepare,
|
||||||
prepare: func() {
|
|
||||||
gock.New(urlLocalhost).
|
|
||||||
Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
|
||||||
},
|
|
||||||
verify: noError,
|
verify: noError,
|
||||||
}, {
|
}, {
|
||||||
name: "normal form request",
|
name: "normal form request",
|
||||||
|
@ -322,14 +314,24 @@ func TestTestCase(t *testing.T) {
|
||||||
Header: map[string]string{
|
Header: map[string]string{
|
||||||
util.ContentType: "application/x-www-form-urlencoded",
|
util.ContentType: "application/x-www-form-urlencoded",
|
||||||
},
|
},
|
||||||
Form: map[string]string{
|
Form: defaultForm,
|
||||||
"key": "value",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
prepare: defaultPostPrepare,
|
||||||
|
verify: noError,
|
||||||
|
}, {
|
||||||
|
name: "body is a template",
|
||||||
|
testCase: &atest.TestCase{
|
||||||
|
Request: atest.Request{
|
||||||
|
API: urlFoo,
|
||||||
|
Method: http.MethodPost,
|
||||||
|
Body: `{"name":"{{lower "HELLO"}}"}`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
prepare: func() {
|
prepare: func() {
|
||||||
gock.New(urlLocalhost).
|
gock.New(urlLocalhost).
|
||||||
Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
Post("/foo").BodyString(`{"name":"hello"}`).
|
||||||
|
Reply(http.StatusOK).BodyString(`{}`)
|
||||||
},
|
},
|
||||||
verify: noError,
|
verify: noError,
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes 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 util provides utilities related to randomization.
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var rng = struct {
|
||||||
|
sync.Mutex
|
||||||
|
rand *rand.Rand
|
||||||
|
}{
|
||||||
|
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
// We omit vowels from the set of available characters to reduce the chances
|
||||||
|
// of "bad words" being formed.
|
||||||
|
alphanums = "bcdfghjklmnpqrstvwxz2456789"
|
||||||
|
// No. of bits required to index into alphanums string.
|
||||||
|
alphanumsIdxBits = 5
|
||||||
|
// Mask used to extract last alphanumsIdxBits of an int.
|
||||||
|
alphanumsIdxMask = 1<<alphanumsIdxBits - 1
|
||||||
|
// No. of random letters we can extract from a single int63.
|
||||||
|
maxAlphanumsPerInt = 63 / alphanumsIdxBits
|
||||||
|
)
|
||||||
|
|
||||||
|
// String generates a random alphanumeric string, without vowels, which is n
|
||||||
|
// characters long. This will panic if n is less than zero.
|
||||||
|
// How the random string is created:
|
||||||
|
// - we generate random int63's
|
||||||
|
// - from each int63, we are extracting multiple random letters by bit-shifting and masking
|
||||||
|
// - if some index is out of range of alphanums we neglect it (unlikely to happen multiple times in a row)
|
||||||
|
func String(n int) string {
|
||||||
|
b := make([]byte, n)
|
||||||
|
rng.Lock()
|
||||||
|
defer rng.Unlock()
|
||||||
|
|
||||||
|
randomInt63 := rng.rand.Int63()
|
||||||
|
remaining := maxAlphanumsPerInt
|
||||||
|
for i := 0; i < n; {
|
||||||
|
if remaining == 0 {
|
||||||
|
randomInt63, remaining = rng.rand.Int63(), maxAlphanumsPerInt
|
||||||
|
}
|
||||||
|
if idx := int(randomInt63 & alphanumsIdxMask); idx < len(alphanums) {
|
||||||
|
b[i] = alphanums[idx]
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
randomInt63 >>= alphanumsIdxBits
|
||||||
|
remaining--
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
Copyright 2015 The Kubernetes 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 util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxRangeTestCount = 500
|
||||||
|
testStringLength = 32
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestString(t *testing.T) {
|
||||||
|
valid := "bcdfghjklmnpqrstvwxz2456789"
|
||||||
|
for _, l := range []int{0, 1, 2, 10, 123} {
|
||||||
|
s := String(l)
|
||||||
|
if len(s) != l {
|
||||||
|
t.Errorf("expected string of size %d, got %q", l, s)
|
||||||
|
}
|
||||||
|
for _, c := range s {
|
||||||
|
if !strings.ContainsRune(valid, c) {
|
||||||
|
t.Errorf("expected valid characters, got %v", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRandomStringGeneration(b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
var s string
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s = String(testStringLength)
|
||||||
|
}
|
||||||
|
b.StopTimer()
|
||||||
|
if len(s) == 0 {
|
||||||
|
b.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ items:
|
||||||
request:
|
request:
|
||||||
api: /api/v1/namespaces/default/configmaps
|
api: /api/v1/namespaces/default/configmaps
|
||||||
header:
|
header:
|
||||||
Authorization: Bearer {{env "K8S_TOKEN"}}
|
Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IkRINXBRRi0zSURrbkRDWGhfVHpEaGFuOVdpcEVLSmFwYUI4Y1V5YjFpcUEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJjbHVzdGVyLWFkbWluLXRva2VuLWtobnI0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImNsdXN0ZXItYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiJmZmNlODg0Ny0yZGY4LTQyMTktOGRjYS1mNGRlMWYzNWNmYzkiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZS1zeXN0ZW06Y2x1c3Rlci1hZG1pbiJ9.YapUNL7aSlAzlZwDqcMF1-eNpaEs0ZPwybV1uM289fDk8RwjHpLQzVZV0IewaOCAjifwyTyqs1Vgd4nF9I7CYPv64cjMcVTQHCj_-pAxXjiYEM9LkR_b__WGsd-3Z0aRrdyO4WS7moRxZ4kz7ULd_OtlHpq-cFIQtytOaQSZNSbxpa5uP7g7y-uv0nwXBSwqZL9j5XimGlYyy999Q8Vc2GLDrDdVp69wuvToODQzJV44nfuA_dhUFQOzC4sE7Dkq7JarrvZspstqLo1ULzt_Z-cZ-qAu_pUaLHkoLZH5o97g4UF8AXeFYLj8YP_IBP9uhDrm829pNHU82N6Hn-80NQ
|
||||||
method: POST
|
method: POST
|
||||||
body: |
|
body: |
|
||||||
{
|
{
|
||||||
|
@ -30,7 +30,10 @@ items:
|
||||||
"kind": "ConfigMap",
|
"kind": "ConfigMap",
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "config",
|
"name": "config",
|
||||||
"namespace": "default"
|
"namespace": "default",
|
||||||
|
"labels": {
|
||||||
|
"key": "{{randomKubernetesName}}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"key": "value"
|
"key": "value"
|
||||||
|
@ -56,8 +59,6 @@ items:
|
||||||
"key": "new value"
|
"key": "new value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect:
|
|
||||||
statusCode: 200
|
|
||||||
- name: get-configmap
|
- name: get-configmap
|
||||||
request:
|
request:
|
||||||
api: /api/v1/namespaces/default/configmaps/config
|
api: /api/v1/namespaces/default/configmaps/config
|
||||||
|
@ -77,7 +78,6 @@ items:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
expect:
|
expect:
|
||||||
statusCode: 200
|
|
||||||
bodyFieldsExpect:
|
bodyFieldsExpect:
|
||||||
"data/key": "new value"
|
"data/key": "new value"
|
||||||
- name: delete-configmap
|
- name: delete-configmap
|
||||||
|
@ -86,5 +86,3 @@ items:
|
||||||
header:
|
header:
|
||||||
Authorization: Bearer {{env "K8S_TOKEN"}}
|
Authorization: Bearer {{env "K8S_TOKEN"}}
|
||||||
method: DELETE
|
method: DELETE
|
||||||
expect:
|
|
||||||
statusCode: 200
|
|
||||||
|
|
Loading…
Reference in New Issue