docs: add releasing process documentation
chore: update go.mod dependencies and versions chore: update go.sum with new dependency versions scripts: add coverage check script scripts: add release notes generation script test: refactor tests to use goconvey for assertions test: enhance webserver tests with additional cases and goconvey fix: improve error logging in web server start and stop methods
This commit is contained in:
+75
-57
@@ -3,7 +3,7 @@ package test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
cfg "scm.yoorie.de/go-lib/micro/config"
|
||||
)
|
||||
|
||||
@@ -13,70 +13,88 @@ type MyConfig struct {
|
||||
Value2 int `default:"8080" yaml:"value2"`
|
||||
}
|
||||
|
||||
func (config *MyConfig) appName() string {
|
||||
func (config *MyConfig) AppName() string {
|
||||
return "myconfig"
|
||||
}
|
||||
|
||||
func TestIsStructPointerStruct(t *testing.T) {
|
||||
assert.False(t, cfg.IsStructPointer(MyConfig{}))
|
||||
}
|
||||
func TestIsStructPointerString(t *testing.T) {
|
||||
assert.False(t, cfg.IsStructPointer("Bar"))
|
||||
}
|
||||
|
||||
func TestIsStructPointerSimpleArray(t *testing.T) {
|
||||
assert.False(t, cfg.IsStructPointer([2]int{292, 2}))
|
||||
}
|
||||
|
||||
func TestIsStructPointerInt(t *testing.T) {
|
||||
assert.False(t, cfg.IsStructPointer(12))
|
||||
}
|
||||
|
||||
func TestIsStructPointerBool(t *testing.T) {
|
||||
assert.False(t, cfg.IsStructPointer(false))
|
||||
}
|
||||
|
||||
func TestIsStructPointerStructPointer(t *testing.T) {
|
||||
assert.True(t, cfg.IsStructPointer(&MyConfig{}))
|
||||
}
|
||||
|
||||
func TestIsStructPointerStructPointerArray(t *testing.T) {
|
||||
cfg1 := &MyConfig{
|
||||
Value: "Foo",
|
||||
}
|
||||
cfg2 := &MyConfig{
|
||||
Value: "Bar",
|
||||
}
|
||||
array := [2]*MyConfig{cfg1, cfg2}
|
||||
assert.False(t, cfg.IsStructPointer(array))
|
||||
}
|
||||
|
||||
func TestLoadConfigurationFromFileWithNoPointerInvalidType(t *testing.T) {
|
||||
myConfig := MyConfig{}
|
||||
err := cfg.LoadConfigurationFromFile(myConfig, "testdata/config.yml")
|
||||
assert.NotNil(t, err)
|
||||
func TestIsStructPointer(t *testing.T) {
|
||||
Convey("IsStructPointer", t, func() {
|
||||
Convey("returns false for struct value", func() {
|
||||
So(cfg.IsStructPointer(MyConfig{}), ShouldBeFalse)
|
||||
})
|
||||
Convey("returns false for string", func() {
|
||||
So(cfg.IsStructPointer("Bar"), ShouldBeFalse)
|
||||
})
|
||||
Convey("returns false for simple array", func() {
|
||||
So(cfg.IsStructPointer([2]int{292, 2}), ShouldBeFalse)
|
||||
})
|
||||
Convey("returns false for int", func() {
|
||||
So(cfg.IsStructPointer(12), ShouldBeFalse)
|
||||
})
|
||||
Convey("returns false for bool", func() {
|
||||
So(cfg.IsStructPointer(false), ShouldBeFalse)
|
||||
})
|
||||
Convey("returns true for struct pointer", func() {
|
||||
So(cfg.IsStructPointer(&MyConfig{}), ShouldBeTrue)
|
||||
})
|
||||
Convey("returns false for struct pointer array", func() {
|
||||
cfg1 := &MyConfig{Value: "Foo"}
|
||||
cfg2 := &MyConfig{Value: "Bar"}
|
||||
array := [2]*MyConfig{cfg1, cfg2}
|
||||
So(cfg.IsStructPointer(array), ShouldBeFalse)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadConfigurationFromFile(t *testing.T) {
|
||||
myConfig := &MyConfig{}
|
||||
assert.Equal(t, "myconfig", myConfig.appName())
|
||||
cfg.LoadConfigurationFromFile(myConfig, "testdata/config.yml")
|
||||
assert.Equal(t, "Foo", myConfig.Value)
|
||||
assert.Equal(t, true, myConfig.Value1)
|
||||
assert.Equal(t, 8080, myConfig.Value2)
|
||||
}
|
||||
func TestLoadConfigurationFromFileNotExist(t *testing.T) {
|
||||
myConfig := &MyConfig{}
|
||||
assert.Equal(t, "myconfig", myConfig.appName())
|
||||
err := cfg.LoadConfigurationFromFile(myConfig, "testdata/config1.yml")
|
||||
assert.NotNil(t, err)
|
||||
Convey("LoadConfigurationFromFile", t, func() {
|
||||
Convey("returns error for non-pointer type", func() {
|
||||
err := cfg.LoadConfigurationFromFile(MyConfig{}, "testdata/config.yml")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("loads config from file successfully", func() {
|
||||
myConfig := &MyConfig{}
|
||||
err := cfg.LoadConfigurationFromFile(myConfig, "testdata/config.yml")
|
||||
So(err, ShouldBeNil)
|
||||
So(myConfig.Value, ShouldEqual, "Foo")
|
||||
So(myConfig.Value1, ShouldBeTrue)
|
||||
So(myConfig.Value2, ShouldEqual, 8080)
|
||||
})
|
||||
Convey("returns error when config file does not exist", func() {
|
||||
myConfig := &MyConfig{}
|
||||
err := cfg.LoadConfigurationFromFile(myConfig, "testdata/config1.yml")
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetConfigurationFiles(t *testing.T) {
|
||||
fileNames := cfg.GetConfigurationFiles("myapp")
|
||||
assert.Equal(t, 4, len(fileNames))
|
||||
Convey("GetConfigurationFiles returns 4 candidate paths", t, func() {
|
||||
fileNames := cfg.GetConfigurationFiles("myapp")
|
||||
So(len(fileNames), ShouldEqual, 4)
|
||||
})
|
||||
}
|
||||
|
||||
for _, fileName := range fileNames {
|
||||
t.Log(fileName)
|
||||
}
|
||||
func TestGetConfigurationFile(t *testing.T) {
|
||||
Convey("GetConfigurationFile", t, func() {
|
||||
Convey("returns empty string when no matching config file exists", func() {
|
||||
result := cfg.GetConfigurationFile("nonexistentapp_xyz_123")
|
||||
So(result, ShouldEqual, "")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoadConfiguration(t *testing.T) {
|
||||
Convey("LoadConfiguration", t, func() {
|
||||
Convey("returns error for non-pointer type", func() {
|
||||
err := cfg.LoadConfiguration(MyConfig{})
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
Convey("returns nil for struct pointer with no config file found", func() {
|
||||
myConfig := &MyConfig{}
|
||||
err := cfg.LoadConfiguration(myConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(myConfig.Value, ShouldEqual, "Bar")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+165
-46
@@ -5,15 +5,15 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
"github.com/stretchr/testify/assert"
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
"scm.yoorie.de/go-lib/micro/web"
|
||||
"scm.yoorie.de/go-lib/util"
|
||||
)
|
||||
@@ -24,6 +24,12 @@ var (
|
||||
sslPort int
|
||||
)
|
||||
|
||||
const (
|
||||
readyMessage = "service is ready"
|
||||
aMessage = "A message"
|
||||
testFailureMsg = "test failure"
|
||||
)
|
||||
|
||||
type MyData struct {
|
||||
Message string `json:"message"`
|
||||
Count int `json:"count"`
|
||||
@@ -32,7 +38,7 @@ type MyData struct {
|
||||
func MyTestEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
internalCount++
|
||||
render.JSON(w, r, &MyData{
|
||||
Message: "A message",
|
||||
Message: aMessage,
|
||||
Count: internalCount,
|
||||
})
|
||||
}
|
||||
@@ -91,7 +97,9 @@ func TestMain(m *testing.M) {
|
||||
panic(err)
|
||||
}
|
||||
server.Mount("/api", CreateTestRouter())
|
||||
server.Start()
|
||||
if err = server.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Allow insecure calls to https
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
@@ -101,56 +109,167 @@ func TestMain(m *testing.M) {
|
||||
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) {
|
||||
uri := getServerURL(false, "/readyz")
|
||||
resp, err := http.Get(uri)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
healthData := web.HealthData{}
|
||||
err = json.Unmarshal(body, &healthData)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "service is ready", healthData.Message)
|
||||
assert.NotEmpty(t, healthData.LastChecked)
|
||||
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) {
|
||||
uri := getServerURL(true, "/health/readyz")
|
||||
resp, err := http.Get(uri)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
healthData := web.HealthData{}
|
||||
err = json.Unmarshal(body, &healthData)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "service is ready", healthData.Message)
|
||||
assert.NotEmpty(t, healthData.LastChecked)
|
||||
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) {
|
||||
resp, err := http.Get(getServerURL(false, "/healthz"))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
healthData := web.HealthData{}
|
||||
err = json.Unmarshal(body, &healthData)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "service up and running", healthData.Message)
|
||||
assert.NotEmpty(t, healthData.LastChecked)
|
||||
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) {
|
||||
uri := getServerURL(true, "/api/myendpoint")
|
||||
resp, err := http.Get(uri)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
assert.Nil(t, err)
|
||||
t.Logf("Body: %s", string(body))
|
||||
myData := MyData{}
|
||||
err = json.Unmarshal(body, &myData)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, myData)
|
||||
assert.Equal(t, "A message", myData.Message)
|
||||
assert.Greater(t, myData.Count, 0)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user