Skip to content

Commit

Permalink
Refactors test API server
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexanderYastrebov committed May 5, 2022
1 parent 1113cf2 commit 41d3be6
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 91 deletions.
94 changes: 94 additions & 0 deletions kubernetes/apitest/apiserver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package apitest

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/ghodss/yaml"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type ApiHandler func(t *testing.T, w http.ResponseWriter, r *http.Request)

type ApiServer struct {
*httptest.Server
t *testing.T
pathPrefix string
handlers map[string]ApiHandler
served map[string]int
}

func NewApiServer(t *testing.T, pathPrefix string, handlers map[string]ApiHandler) *ApiServer {
s := &ApiServer{
t: t,
pathPrefix: pathPrefix,
handlers: handlers,
served: make(map[string]int),
}
s.Server = httptest.NewServer(s)
return s
}

func (s *ApiServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
op := fmt.Sprintf("%s %s", r.Method, strings.TrimPrefix(r.URL.Path, s.pathPrefix))

if handler, ok := s.handlers[op]; ok {
handler(s.t, w, r)
s.served[op]++
} else {
http.Error(w, "unsupported operation", http.StatusInternalServerError)
}
}

func (s *ApiServer) Close() {
s.Server.Close()

s.t.Helper()

expected := make(map[string]int)
for op := range s.handlers {
expected[op] = 1
}
assert.Equal(s.t, expected, s.served)
}

func JsonFromYamlHandler(filename string) ApiHandler {
return func(_ *testing.T, w http.ResponseWriter, _ *http.Request) {
bytes, err := yamlToJson(filename)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
_, err = w.Write(bytes)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}

func ExpectJsonBodyAsYaml(t *testing.T, r *http.Request, filename string) {
body, err := io.ReadAll(r.Body)
require.NoError(t, err)

expected, err := yamlToJson(filename)
require.NoError(t, err)

assert.JSONEq(t, string(expected), string(body))
}

func yamlToJson(filename string) ([]byte, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
return yaml.YAMLToJSON(bytes)
}
106 changes: 15 additions & 91 deletions kubernetes/dns_test.go
Original file line number Diff line number Diff line change
@@ -1,40 +1,18 @@
package kubernetes

import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"

"github.com/ghodss/yaml"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zalando-incubator/kube-ingress-aws-controller/kubernetes/apitest"
)

type testApiHandler func(t *testing.T, w http.ResponseWriter, r *http.Request)

type testApiServer struct {
*httptest.Server
neverCalled map[string]struct{}
}

func (s *testApiServer) done(t *testing.T) {
t.Helper()
if len(s.neverCalled) > 0 {
t.Errorf("some handlers never called: %v", s.neverCalled)
}
s.Close()
}

func TestUpdateHostnames(t *testing.T) {
for _, test := range []struct {
name string
hostnames map[string]string
handlers map[string]testApiHandler
handlers map[string]apitest.ApiHandler
expectedError string
}{
{
Expand All @@ -43,12 +21,12 @@ func TestUpdateHostnames(t *testing.T) {
"foo.test": "kube-ing-lb-1",
"bar.test": "kube-ing-lb-2",
},
handlers: map[string]testApiHandler{
handlers: map[string]apitest.ApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": func(t *testing.T, w http.ResponseWriter, r *http.Request) {
http.Error(w, "Not Found", http.StatusNotFound)
},
"POST /kube-system/dnsendpoints": func(t *testing.T, w http.ResponseWriter, r *http.Request) {
verifyJsonBodyAsYaml(t, r, "testdata/dns/foo-bar.yaml")
apitest.ExpectJsonBodyAsYaml(t, r, "testdata/dns/foo-bar.yaml")
w.WriteHeader(http.StatusCreated)
},
},
Expand All @@ -60,10 +38,10 @@ func TestUpdateHostnames(t *testing.T) {
"bar.test": "kube-ing-lb-3",
"baz.test": "kube-ing-lb-3",
},
handlers: map[string]testApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": serveYamlAsJson("testdata/dns/foo-bar.yaml"),
handlers: map[string]apitest.ApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": apitest.JsonFromYamlHandler("testdata/dns/foo-bar.yaml"),
"PATCH /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": func(t *testing.T, w http.ResponseWriter, r *http.Request) {
verifyJsonBodyAsYaml(t, r, "testdata/dns/foo-bar-baz-patch.yaml")
apitest.ExpectJsonBodyAsYaml(t, r, "testdata/dns/foo-bar-baz-patch.yaml")
},
},
},
Expand All @@ -73,8 +51,8 @@ func TestUpdateHostnames(t *testing.T) {
"foo.test": "kube-ing-lb-1",
"bar.test": "kube-ing-lb-2",
},
handlers: map[string]testApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": serveYamlAsJson("testdata/dns/foo-bar.yaml"),
handlers: map[string]apitest.ApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": apitest.JsonFromYamlHandler("testdata/dns/foo-bar.yaml"),
},
expectedError: "resource update not needed",
},
Expand All @@ -84,7 +62,7 @@ func TestUpdateHostnames(t *testing.T) {
"foo.test": "kube-ing-lb-1",
"bar.test": "kube-ing-lb-2",
},
handlers: map[string]testApiHandler{
handlers: map[string]apitest.ApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": func(t *testing.T, w http.ResponseWriter, r *http.Request) {
http.Error(w, "oops", http.StatusInternalServerError)
},
Expand All @@ -98,8 +76,8 @@ func TestUpdateHostnames(t *testing.T) {
"bar.test": "kube-ing-lb-3",
"baz.test": "kube-ing-lb-3",
},
handlers: map[string]testApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": serveYamlAsJson("testdata/dns/foo-bar.yaml"),
handlers: map[string]apitest.ApiHandler{
"GET /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": apitest.JsonFromYamlHandler("testdata/dns/foo-bar.yaml"),
"PATCH /kube-system/dnsendpoints/kube-ingress-aws-controller-dns": func(t *testing.T, w http.ResponseWriter, r *http.Request) {
http.Error(w, "oops", http.StatusInternalServerError)
},
Expand All @@ -108,10 +86,10 @@ func TestUpdateHostnames(t *testing.T) {
},
} {
t.Run(test.name, func(t *testing.T) {
testApiServer := newTestApiServer(t, test.handlers)
defer testApiServer.done(t)
s := apitest.NewApiServer(t, "/apis/externaldns.k8s.io/v1alpha1/namespaces", test.handlers)
defer s.Close()

cfg := &Config{BaseURL: testApiServer.URL}
cfg := &Config{BaseURL: s.URL}
kubeClient, _ := newSimpleClient(cfg, false)

err := updateHostnames(kubeClient, test.hostnames)
Expand All @@ -123,57 +101,3 @@ func TestUpdateHostnames(t *testing.T) {
})
}
}

func newTestApiServer(t *testing.T, handlers map[string]testApiHandler) *testApiServer {
s := &testApiServer{}
s.neverCalled = make(map[string]struct{})
for op, _ := range handlers {
s.neverCalled[op] = struct{}{}
}
s.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
op := fmt.Sprintf("%s %s", r.Method, strings.TrimPrefix(r.URL.Path, "/apis/externaldns.k8s.io/v1alpha1/namespaces"))

if handler, ok := handlers[op]; ok {
handler(t, w, r)
delete(s.neverCalled, op)
} else {
http.Error(w, "unsupported operation", http.StatusInternalServerError)
}
}))
return s
}

func serveYamlAsJson(path string) testApiHandler {
return func(_ *testing.T, w http.ResponseWriter, _ *http.Request) {
bytes, err := yamlAsJson(path)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

w.WriteHeader(http.StatusOK)
_, err = w.Write(bytes)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
}

func yamlAsJson(path string) ([]byte, error) {
bytes, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return yaml.YAMLToJSON(bytes)
}

func verifyJsonBodyAsYaml(t *testing.T, r *http.Request, path string) {
body, err := io.ReadAll(r.Body)
require.NoError(t, err)

expected, err := yamlAsJson(path)
require.NoError(t, err)

assert.JSONEq(t, string(expected), string(body))
}

0 comments on commit 41d3be6

Please sign in to comment.