276 lines
7.1 KiB
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)
|
|
})
|
|
}
|