package builder
import (
"crypto/tls"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"git.sr.ht/~mna/kick"
"github.com/unrolled/secure"
)
func TestTLS_Intermediate(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
AutoCert: true,
Mode: kick.TLSIntermediate,
},
}
conf, err := TLS(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
if !conf.PreferServerCipherSuites {
t.Fatalf("want PreferServerCipherSuites to be true")
}
}
func TestTLS_Modern(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
AutoCert: true,
Mode: kick.TLSModern,
},
}
conf, err := TLS(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
if !conf.PreferServerCipherSuites {
t.Fatalf("want PreferServerCipherSuites to be true")
}
if conf.MinVersion != tls.VersionTLS12 {
t.Fatalf("want MinVersion to be %v, got %v", tls.VersionTLS12, conf.MinVersion)
}
}
func TestTLS_InvalidMode(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
AutoCert: true,
Mode: kick.TLSModern + 1000,
},
}
_, err := TLS(s)
if err == nil {
t.Fatalf("want error, got none")
}
if !strings.Contains(err.Error(), "tls: unsupported TLS mode") {
t.Fatalf("unexpected error message: %s", err)
}
}
func TestTLS_AutoCert(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
AutoCert: true,
},
}
conf, err := TLS(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
if conf.GetCertificate == nil {
t.Fatalf("want GetCertificate to be set")
}
}
func TestTLS_ValidCert(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
CertFile: os.Getenv("KICK_TEST_LOCALHOST_CERT"),
KeyFile: os.Getenv("KICK_TEST_LOCALHOST_KEY"),
},
}
conf, err := TLS(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
if len(conf.Certificates) != 1 {
t.Fatalf("want 1 certificate, got %d", len(conf.Certificates))
}
}
func TestTLS_InvalidCert(t *testing.T) {
s := &kick.Server{
TLS: &kick.TLSConfig{
CertFile: os.Getenv("KICK_TEST_LOCALHOST_CERT"),
},
}
_, err := TLS(s)
if err == nil {
t.Fatalf("want error, got none")
}
if !strings.Contains(err.Error(), "tls: certificate and key files") {
t.Fatalf("unexpected error message: %s", err)
}
}
func TestTLS_NoConfig(t *testing.T) {
s := new(kick.Server)
conf, err := TLS(s)
if conf != nil {
t.Fatalf("want no config, got %v", conf)
}
if err != nil {
t.Fatalf("want no error, got %s", err)
}
}
func TestHTTPServer(t *testing.T) {
s := &kick.Server{
Addr: ":1234",
}
srv, err := HTTPServer(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
if srv.Addr != s.Addr {
t.Fatalf("want address %s, got %s", s.Addr, srv.Addr)
}
}
func TestHandler_InvalidCanonicalRedirect(t *testing.T) {
s := &kick.Server{
Root: &kick.Root{
MethodNotAllowedHandler: statusHandler(405),
NotFoundHandler: statusHandler(404),
PanicRecoveryFunc: func(w http.ResponseWriter, r *http.Request, v interface{}) {},
TrustProxyHeaders: true,
AllowMethodOverride: true,
RequestIDHeader: "X",
LoggingFunc: func(w http.ResponseWriter, r *http.Request, info map[string]interface{}) {},
CanonicalHost: "abc",
CanonicalHostRedirectStatusCode: 500,
},
}
_, err := Handler(s)
if err == nil {
t.Fatalf("want error, got none")
}
if !strings.Contains(err.Error(), "canonical host redirection") {
t.Fatalf("unexpected error message: %s", err)
}
}
func TestHandler_InvalidGzip(t *testing.T) {
s := &kick.Server{
Root: &kick.Root{
CanonicalHost: "abc",
CanonicalHostRedirectStatusCode: 301,
Gzip: &kick.GzipConfig{
CompressionLevel: -1000,
},
},
}
_, err := Handler(s)
if err == nil {
t.Fatalf("want error, got none")
}
if !strings.Contains(err.Error(), "invalid compression level") {
t.Fatalf("unexpected error message: %s", err)
}
}
func TestHandler_RouteNoConfig(t *testing.T) {
s := &kick.Server{
Root: &kick.Root{
TrustProxyHeaders: true,
},
Routes: []*kick.Route{
{
Path: "/",
Method: "GET",
Handler: statusHandler(200),
},
},
}
h, err := Handler(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
w := httptest.NewRecorder()
r, _ := http.NewRequest("", "/", nil)
h.ServeHTTP(w, r)
if w.Code != 200 {
t.Fatalf("want status 200, got %d", w.Code)
}
}
func TestHandler_InvalidRoute(t *testing.T) {
cases := []struct {
route *kick.Route
errMsg string
}{
{
&kick.Route{
Path: "",
Method: "GET",
Handler: statusHandler(200),
},
"must have a path",
},
{
&kick.Route{
Path: "X",
Method: "GET",
Handler: statusHandler(200),
},
"must start with a slash",
},
{
&kick.Route{
Path: "/a",
Method: "",
Handler: statusHandler(200),
},
"method is missing",
},
{
&kick.Route{
Path: "/a",
Method: "GET",
Handler: nil,
},
"handler is missing",
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%s %s", c.route.Method, c.route.Path), func(t *testing.T) {
s := &kick.Server{
Routes: []*kick.Route{
c.route,
},
}
_, err := Handler(s)
if err == nil {
t.Fatalf("want error, got none")
}
if !strings.Contains(err.Error(), c.errMsg) {
t.Fatalf("unexpected error message: %s", err)
}
})
}
}
func TestHandler_Valid(t *testing.T) {
s := &kick.Server{
Root: &kick.Root{
MethodNotAllowedHandler: statusHandler(405),
NotFoundHandler: statusHandler(404),
PanicRecoveryFunc: func(w http.ResponseWriter, r *http.Request, v interface{}) {
w.WriteHeader(500)
},
TrustProxyHeaders: true,
AllowMethodOverride: true,
RequestIDHeader: "X-Request-Id",
SecurityHeaders: &secure.Options{
FrameDeny: true,
},
Gzip: &kick.GzipConfig{
ContentTypes: []string{"application/xml"},
},
},
Routes: []*kick.Route{
{
Method: "GET",
Path: "/",
Handler: statusHandler(200),
},
{
Method: "POST",
Path: "/panic",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic(io.EOF)
}),
},
},
}
h, err := Handler(s)
if err != nil {
t.Fatalf("want no error, got %s", err)
}
cases := []struct {
req string // formatted as "METHOD /path/... body"
reqh string // formatted as "Name:Value Name:Value"
code int
resh string // formatted as "Name:Value", * means any value
}{
{
req: "GET /",
code: 200,
resh: "X-Request-Id:*",
},
}
for _, c := range cases {
t.Run(fmt.Sprintf("%s %s", c.req, c.reqh), func(t *testing.T) {
_ = h
})
}
}