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 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
|
||||
|
||||
It could verify any kinds of Kubernetes resources. Please set the environment variables before using it:
|
||||
|
|
|
@ -6,12 +6,19 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
"github.com/linuxsuren/api-testing/pkg/util"
|
||||
)
|
||||
|
||||
// Render render then return the result
|
||||
func Render(name, text string, ctx interface{}) (result string, err error) {
|
||||
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)
|
||||
if err = tpl.Execute(buf, ctx); err == nil {
|
||||
result = strings.TrimSpace(buf.String())
|
||||
|
|
|
@ -12,6 +12,7 @@ func TestRender(t *testing.T) {
|
|||
text string
|
||||
ctx interface{}
|
||||
expect string
|
||||
verify func(*testing.T, string)
|
||||
}{{
|
||||
name: "default",
|
||||
text: `{{default "hello" .Bar}}`,
|
||||
|
@ -22,12 +23,23 @@ func TestRender(t *testing.T) {
|
|||
text: `{{trim " hello "}}`,
|
||||
ctx: "",
|
||||
expect: "hello",
|
||||
}, {
|
||||
name: "randomKubernetesName",
|
||||
text: `{{randomKubernetesName}}`,
|
||||
verify: func(t *testing.T, s string) {
|
||||
assert.Equal(t, 8, len(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)
|
||||
assert.Equal(t, tt.expect, result)
|
||||
if tt.expect != "" {
|
||||
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 requestBody, err = testcase.Request.GetBody(); err != nil {
|
||||
if err = testcase.Request.Render(dataContext); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = testcase.Request.Render(dataContext); err != nil {
|
||||
var requestBody io.Reader
|
||||
if requestBody, err = testcase.Request.GetBody(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,17 @@ func TestTestCase(t *testing.T) {
|
|||
fooRequst := atest.Request{
|
||||
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 {
|
||||
name string
|
||||
|
@ -41,11 +52,9 @@ func TestTestCase(t *testing.T) {
|
|||
name: "normal, response is map",
|
||||
testCase: &atest.TestCase{
|
||||
Request: atest.Request{
|
||||
API: urlFoo,
|
||||
Header: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Body: `{"foo":"bar"}`,
|
||||
API: urlFoo,
|
||||
Header: defaultForm,
|
||||
Body: `{"foo":"bar"}`,
|
||||
},
|
||||
Expect: atest.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
|
@ -204,10 +213,7 @@ func TestTestCase(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
gock.New(urlLocalhost).
|
||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
},
|
||||
prepare: defaultPrepare,
|
||||
verify: func(t *testing.T, output interface{}, err error) {
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to get field")
|
||||
|
@ -225,10 +231,7 @@ func TestTestCase(t *testing.T) {
|
|||
// },
|
||||
// },
|
||||
// },
|
||||
// prepare: func() {
|
||||
// gock.New(urlLocalhost).
|
||||
// Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
// },
|
||||
// prepare: defaultPrepare,
|
||||
// verify: func(t *testing.T, output interface{}, err error) {
|
||||
// if assert.NotNil(t, err) {
|
||||
// assert.Contains(t, err.Error(), "failed to verify")
|
||||
|
@ -245,10 +248,7 @@ func TestTestCase(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
gock.New(urlLocalhost).
|
||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
},
|
||||
prepare: defaultPrepare,
|
||||
verify: func(t *testing.T, output interface{}, err error) {
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "unknown name println")
|
||||
|
@ -263,10 +263,7 @@ func TestTestCase(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
gock.New(urlLocalhost).
|
||||
Get("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
},
|
||||
prepare: defaultPrepare,
|
||||
verify: func(t *testing.T, output interface{}, err error) {
|
||||
assert.NotNil(t, err)
|
||||
assert.Contains(t, err.Error(), "expected bool, but got int")
|
||||
|
@ -303,16 +300,11 @@ func TestTestCase(t *testing.T) {
|
|||
Header: map[string]string{
|
||||
util.ContentType: "multipart/form-data",
|
||||
},
|
||||
Form: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Form: defaultForm,
|
||||
},
|
||||
},
|
||||
prepare: func() {
|
||||
gock.New(urlLocalhost).
|
||||
Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
},
|
||||
verify: noError,
|
||||
prepare: defaultPostPrepare,
|
||||
verify: noError,
|
||||
}, {
|
||||
name: "normal form request",
|
||||
testCase: &atest.TestCase{
|
||||
|
@ -322,14 +314,24 @@ func TestTestCase(t *testing.T) {
|
|||
Header: map[string]string{
|
||||
util.ContentType: "application/x-www-form-urlencoded",
|
||||
},
|
||||
Form: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
Form: defaultForm,
|
||||
},
|
||||
},
|
||||
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() {
|
||||
gock.New(urlLocalhost).
|
||||
Post("/foo").Reply(http.StatusOK).BodyString(`{"items":[]}`)
|
||||
Post("/foo").BodyString(`{"name":"hello"}`).
|
||||
Reply(http.StatusOK).BodyString(`{}`)
|
||||
},
|
||||
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:
|
||||
api: /api/v1/namespaces/default/configmaps
|
||||
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
|
||||
body: |
|
||||
{
|
||||
|
@ -30,7 +30,10 @@ items:
|
|||
"kind": "ConfigMap",
|
||||
"metadata": {
|
||||
"name": "config",
|
||||
"namespace": "default"
|
||||
"namespace": "default",
|
||||
"labels": {
|
||||
"key": "{{randomKubernetesName}}"
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"key": "value"
|
||||
|
@ -56,8 +59,6 @@ items:
|
|||
"key": "new value"
|
||||
}
|
||||
}
|
||||
expect:
|
||||
statusCode: 200
|
||||
- name: get-configmap
|
||||
request:
|
||||
api: /api/v1/namespaces/default/configmaps/config
|
||||
|
@ -77,7 +78,6 @@ items:
|
|||
}
|
||||
}
|
||||
expect:
|
||||
statusCode: 200
|
||||
bodyFieldsExpect:
|
||||
"data/key": "new value"
|
||||
- name: delete-configmap
|
||||
|
@ -86,5 +86,3 @@ items:
|
|||
header:
|
||||
Authorization: Bearer {{env "K8S_TOKEN"}}
|
||||
method: DELETE
|
||||
expect:
|
||||
statusCode: 200
|
||||
|
|
Loading…
Reference in New Issue