micro/test/webserver_test.go

276 lines
7.1 KiB
Go

package test
import (
"crypto/tls"
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
"testing"
"time"
"github.com/go-chi/chi/v5"
"github.com/go-chi/render"
. "github.com/smartystreets/goconvey/convey"
"scm.yoorie.de/go-lib/micro/web"
"scm.yoorie.de/go-lib/util"
)
var (
internalCount int
httpPort int
sslPort int
)
const (
readyMessage = "service is ready"
aMessage = "A message"
testFailureMsg = "test failure"
)
type MyData struct {
Message string `json:"message"`
Count int `json:"count"`
}
func MyTestEndpoint(w http.ResponseWriter, r *http.Request) {
internalCount++
render.JSON(w, r, &MyData{
Message: aMessage,
Count: internalCount,
})
}
func CreateTestRouter() *chi.Mux {
router := chi.NewRouter()
router.Get("/myendpoint", MyTestEndpoint)
return router
}
// GetFreePort asks the kernel for a free open port that is ready to use.
func GetFreePort() (int, error) {
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
if err != nil {
return 0, err
}
l, err := net.ListenTCP("tcp", addr)
if err != nil {
return 0, err
}
defer l.Close()
return l.Addr().(*net.TCPAddr).Port, nil
}
func getServerURL(ssl bool, path string) string {
var scheme string
var port int
if ssl {
scheme = "https"
port = sslPort
} else {
scheme = "http"
port = httpPort
}
return util.JoiningSlash(fmt.Sprintf("%s://localhost:%d", scheme, port), path)
}
func TestMain(m *testing.M) {
internalCount = 0
var err error
httpPort, err = GetFreePort()
if err != nil {
panic(err)
}
sslPort, err = GetFreePort()
if err != nil {
panic(err)
}
config := &web.WebServerConfiguration{
Port: httpPort,
SslPort: sslPort,
}
server, err := web.NewWebServer(config)
if err != nil {
panic(err)
}
server.Mount("/api", CreateTestRouter())
if err = server.Start(); err != nil {
panic(err)
}
// Allow insecure calls to https
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
exitVal := m.Run()
server.Stop()
os.Exit(exitVal)
}
func TestNewWebServerNilConfig(t *testing.T) {
Convey("NewWebServer with nil config returns error", t, func() {
server, err := web.NewWebServer(nil)
So(err, ShouldNotBeNil)
So(server, ShouldBeNil)
})
}
func TestGetBindings(t *testing.T) {
Convey("WebServerConfiguration.GetBindings", t, func() {
Convey("returns only http binding when SSL port is 0", func() {
config := &web.WebServerConfiguration{Port: 8080}
So(config.GetBindings(), ShouldEqual, "http://0.0.0.0:8080")
})
Convey("returns https and http bindings when SSL port is set", func() {
config := &web.WebServerConfiguration{Port: 8080, SslPort: 8443}
So(config.GetBindings(), ShouldEqual, "https://0.0.0.0:8443,http://0.0.0.0:8080")
})
})
}
func TestBuildHostAddress(t *testing.T) {
Convey("WebServerConfiguration.BuildHostAddress", t, func() {
Convey("uses 0.0.0.0 when host is empty", func() {
config := &web.WebServerConfiguration{Port: 8080, SslPort: 8443}
So(config.BuildHostAddress(false), ShouldEqual, "0.0.0.0:8080")
So(config.BuildHostAddress(true), ShouldEqual, "0.0.0.0:8443")
})
Convey("uses configured host when set", func() {
config := &web.WebServerConfiguration{Host: "192.168.1.1", Port: 8080, SslPort: 8443}
So(config.BuildHostAddress(false), ShouldEqual, "192.168.1.1:8080")
So(config.BuildHostAddress(true), ShouldEqual, "192.168.1.1:8443")
})
})
}
func TestStartWithNoMounts(t *testing.T) {
Convey("Start returns error when no mounts are registered", t, func() {
port, err := GetFreePort()
So(err, ShouldBeNil)
config := &web.WebServerConfiguration{Port: port}
server, err := web.NewWebServer(config)
So(err, ShouldBeNil)
err = server.Start()
So(err, ShouldNotBeNil)
})
}
func TestNonSSLServer(t *testing.T) {
Convey("Non-SSL web server starts, serves requests, and stops", t, func() {
port, err := GetFreePort()
So(err, ShouldBeNil)
config := &web.WebServerConfiguration{Port: port}
server, err := web.NewWebServer(config)
So(err, ShouldBeNil)
server.Mount("/api", CreateTestRouter())
err = server.Start()
So(err, ShouldBeNil)
time.Sleep(100 * time.Millisecond)
uri := fmt.Sprintf("http://localhost:%d/health/readyz", port)
resp, err := http.Get(uri)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusOK)
body, _ := io.ReadAll(resp.Body)
healthData := web.HealthData{}
err = json.Unmarshal(body, &healthData)
So(err, ShouldBeNil)
So(healthData.Message, ShouldEqual, readyMessage)
server.Stop()
})
}
func TestUnhealthyServer(t *testing.T) {
Convey("Server with failing HealthCheck returns 503 from healthz endpoint", t, func() {
port, err := GetFreePort()
So(err, ShouldBeNil)
config := &web.WebServerConfiguration{Port: port}
server, err := web.NewWebServer(config)
So(err, ShouldBeNil)
server.HealthCheck = func() (bool, string) { return false, testFailureMsg }
server.Mount("/api", CreateTestRouter())
err = server.Start()
So(err, ShouldBeNil)
time.Sleep(100 * time.Millisecond)
uri := fmt.Sprintf("http://localhost:%d/health/healthz", port)
resp, err := http.Get(uri)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusServiceUnavailable)
server.Stop()
})
}
func TestReady(t *testing.T) {
Convey("HTTP ready endpoint returns 200 with ready message", t, func() {
uri := getServerURL(false, "/readyz")
resp, err := http.Get(uri)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusOK)
body, _ := io.ReadAll(resp.Body)
healthData := web.HealthData{}
err = json.Unmarshal(body, &healthData)
So(err, ShouldBeNil)
So(healthData.Message, ShouldEqual, readyMessage)
So(healthData.LastChecked, ShouldNotBeZeroValue)
})
}
func TestReadySSL(t *testing.T) {
Convey("HTTPS ready endpoint returns 200 with ready message", t, func() {
uri := getServerURL(true, "/health/readyz")
resp, err := http.Get(uri)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusOK)
body, _ := io.ReadAll(resp.Body)
healthData := web.HealthData{}
err = json.Unmarshal(body, &healthData)
So(err, ShouldBeNil)
So(healthData.Message, ShouldEqual, readyMessage)
So(healthData.LastChecked, ShouldNotBeZeroValue)
})
}
func TestHealthy(t *testing.T) {
Convey("Healthy endpoint returns 200 with running message", t, func() {
resp, err := http.Get(getServerURL(false, "/healthz"))
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusOK)
body, _ := io.ReadAll(resp.Body)
healthData := web.HealthData{}
err = json.Unmarshal(body, &healthData)
So(err, ShouldBeNil)
So(healthData.Message, ShouldEqual, "service up and running")
So(healthData.LastChecked, ShouldNotBeZeroValue)
})
}
func TestSslEndpoint(t *testing.T) {
Convey("SSL endpoint returns 200 with expected data", t, func() {
uri := getServerURL(true, "/api/myendpoint")
resp, err := http.Get(uri)
So(err, ShouldBeNil)
So(resp.StatusCode, ShouldEqual, http.StatusOK)
body, err := io.ReadAll(resp.Body)
So(err, ShouldBeNil)
myData := MyData{}
err = json.Unmarshal(body, &myData)
So(err, ShouldBeNil)
So(myData.Message, ShouldEqual, aMessage)
So(myData.Count, ShouldBeGreaterThan, 0)
})
}