Skip to content

Commit

Permalink
fat: add template function randomKubernetesName (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen authored Apr 24, 2023
1 parent 607818f commit 9df7a66
Show file tree
Hide file tree
Showing 8 changed files with 196 additions and 45 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
9 changes: 8 additions & 1 deletion pkg/render/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
14 changes: 13 additions & 1 deletion pkg/render/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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}}`,
Expand All @@ -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)
}
})
}
}
6 changes: 3 additions & 3 deletions pkg/runner/simple.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
68 changes: 35 additions & 33 deletions pkg/runner/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand All @@ -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")
Expand Down Expand Up @@ -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{
Expand All @@ -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,
}}
Expand Down
70 changes: 70 additions & 0 deletions pkg/util/rand.go
Original file line number Diff line number Diff line change
@@ -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)
}
54 changes: 54 additions & 0 deletions pkg/util/rand_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
12 changes: 5 additions & 7 deletions sample/kubernetes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ 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: |
{
"apiVersion": "v1",
"kind": "ConfigMap",
"metadata": {
"name": "config",
"namespace": "default"
"namespace": "default",
"labels": {
"key": "{{randomKubernetesName}}"
}
},
"data": {
"key": "value"
Expand All @@ -56,8 +59,6 @@ items:
"key": "new value"
}
}
expect:
statusCode: 200
- name: get-configmap
request:
api: /api/v1/namespaces/default/configmaps/config
Expand All @@ -77,7 +78,6 @@ items:
}
}
expect:
statusCode: 200
bodyFieldsExpect:
"data/key": "new value"
- name: delete-configmap
Expand All @@ -86,5 +86,3 @@ items:
header:
Authorization: Bearer {{env "K8S_TOKEN"}}
method: DELETE
expect:
statusCode: 200

0 comments on commit 9df7a66

Please sign in to comment.