@@ -1,368 +1 @@ | |||
// This is the unit test package for Capstan. It provides a high level series of | |||
// usage tests that exercise its exposed functionality as per endpoints and | |||
// their respective configurations. Consequently, this test may be used in | |||
// addition to Capstan's documentation as each test is segregated from the | |||
// others and performed (mostly) in isolation. A single server instance is spun | |||
// up to run internally, but each test has its own endpoint. | |||
// | |||
// Other packages provide more fine-grained unit tests to validate correctness; | |||
// this package should be considered a test of the public API. | |||
package capstan_test | |||
import ( | |||
"bytes" | |||
"net/http" | |||
"net/http/httptest" | |||
"os" | |||
"strings" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan/tests" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
var server *httptest.Server | |||
type testMethods struct { | |||
HasBody bool | |||
Method func(string) *please.ClientRequest | |||
} | |||
type testBody struct { | |||
Code int | |||
Body []byte | |||
} | |||
type testBodyMethod struct { | |||
Code int | |||
Body []byte | |||
Method string | |||
} | |||
func TestMain(m *testing.M) { | |||
server = tests.Server() | |||
defer server.Close() | |||
os.Exit(m.Run()) | |||
} | |||
// Test_MethodEndpoints tests all method endpoints: | |||
// | |||
// - Get | |||
// - Head | |||
// - Post | |||
// - Put | |||
// - Patch | |||
// - Delete | |||
// - Options | |||
// - Trace | |||
// - Connect | |||
func Test_MethodEndpoints(t *testing.T) { | |||
var out []byte | |||
client := server.Client() | |||
client.CheckRedirect = nil | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(server.Client()) | |||
d := map[string]*testMethods{ | |||
"GET": &testMethods{true, requester.Get}, | |||
"POST": &testMethods{true, requester.Post}, | |||
"PUT": &testMethods{true, requester.Put}, | |||
"PATCH": &testMethods{true, requester.Patch}, | |||
"DELETE": &testMethods{true, requester.Delete}, | |||
"HEAD": &testMethods{false, requester.Head}, | |||
} | |||
for k, v := range d { | |||
req := v.Method("/methods") | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s response error: %v", k, errors.Unfurl(err)) | |||
} | |||
out, err = response.ReadAll() | |||
if err != nil { | |||
t.Fatalf("%s read body error: %v", k, err) | |||
} | |||
if v.HasBody && bytes.Compare(out, []byte(k)) != 0 { | |||
t.Errorf("%s returned unexpected body: %v", k, string(out)) | |||
} | |||
if response.Headers().Get("X-Test-Method") != k { | |||
t.Errorf("%s did not return correct X-Test-Method header", k) | |||
} | |||
} | |||
} | |||
func Test_Redirects(t *testing.T) { | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
req := requester.Get("/redirect/external") | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("response error during redirect: %v", errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != 301 { | |||
t.Fatalf("received non-redirect status code %d", response.StatusCode) | |||
} | |||
if h := response.Headers().Get("location"); h == "" { | |||
t.Fatal("expected location header missing") | |||
} else { | |||
if !strings.Contains(h, server.URL+"/methods") { | |||
t.Fatalf("unexpected redirection path: %s", h) | |||
} | |||
} | |||
req = requester.Get("/redirect/internal") | |||
response, err = req.Commit() | |||
if err != nil { | |||
t.Fatalf("response error during redirect: %v", errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != 301 { | |||
t.Fatalf("received non-redirect status code %d", response.StatusCode) | |||
} | |||
if h := response.Headers().Get("location"); h == "" { | |||
t.Fatal("expected location header missing") | |||
} else { | |||
if !strings.Contains(h, "/methods") { | |||
t.Fatalf("unexpected redirection path: %s", h) | |||
} | |||
} | |||
} | |||
func Test_IndexController(t *testing.T) { | |||
client := server.Client() | |||
client.CheckRedirect = nil | |||
tests := map[string]*testBody{ | |||
// Optional trailing slash ("/?"). | |||
"/index": &testBody{200, []byte("INDEX")}, | |||
"/index/": &testBody{200, []byte("INDEX")}, | |||
"/index/testing": &testBody{200, []byte("testing")}, | |||
"/index/testing/": &testBody{200, []byte("testing")}, | |||
// Mandatory slash ("/"). | |||
"/index-slash": &testBody{404, nil}, | |||
"/index-slash/": &testBody{200, []byte("INDEX")}, | |||
"/index-slash/testing": &testBody{404, nil}, | |||
"/index-slash/testing/": &testBody{200, []byte("testing")}, | |||
// Mandatory no-slash exclamation ("!"). | |||
"/index-no-slash": &testBody{200, []byte("INDEX")}, | |||
"/index-no-slash/": &testBody{404, nil}, | |||
"/index-no-slash/testing": &testBody{200, []byte("testing")}, | |||
"/index-no-slash/testing/": &testBody{404, nil}, | |||
// Mandatory no-slash exclamation plus slash ("/!"). | |||
"/index-no-slash-slash": &testBody{200, []byte("INDEX")}, | |||
"/index-no-slash-slash/": &testBody{404, nil}, | |||
"/index-no-slash-slash/testing": &testBody{200, []byte("testing")}, | |||
"/index-no-slash-slash/testing/": &testBody{404, nil}, | |||
// Test index-specific route. | |||
"/index-sub-index/index": &testBody{200, []byte("INDEX")}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
out, err := response.ReadAll() | |||
if err != nil { | |||
t.Fatalf("%s: error reading body: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
// TODO: A nil body indicates we're not going to do a complete | |||
// comparison at this time. | |||
if body.Body != nil && bytes.Compare(out, body.Body) != 0 { | |||
t.Fatalf(`%s: unexpected body: "%v"`, url, | |||
string(out)) | |||
} | |||
} | |||
} | |||
func Test_ParameterizedRedirects(t *testing.T) { | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBody{ | |||
"/param-redirect-slash/testing1": &testBody{301, nil}, | |||
"/param-redirect-slash/testing1/": &testBody{200, []byte("testing1")}, | |||
"/param-redirect-no-slash/testing2": &testBody{200, []byte("testing2")}, | |||
"/param-redirect-no-slash/testing2/": &testBody{301, nil}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} | |||
func Test_CustomBaseRoutes(t *testing.T) { | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBody{ | |||
"/custom-base-route/demo": &testBody{200, []byte("DEMO")}, | |||
"/custom-base-route/custom-route-1": &testBody{200, []byte("CUSTOM-1")}, | |||
"/custom-base-route/custom-route-2": &testBody{200, []byte("CUSTOM-N")}, | |||
"/custom-base-route/custom-route-2/": &testBody{404, nil}, | |||
"/custom-base-route/custom-route-3": &testBody{301, nil}, | |||
"/custom-base-route/custom-route-3/": &testBody{200, []byte("CUSTOM-O")}, | |||
"/custom-base-route/custom-route-4": &testBody{404, nil}, | |||
"/custom-base-route/custom-route-4/": &testBody{200, []byte("CUSTOM-S")}, | |||
"/custom-base-route/custom-route-underscore": &testBody{200, []byte("CUSTOM_UNDERSCORE")}, | |||
"/custom-base-route/custom-base-route-abuse-1-a": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-2a": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-3-z": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-4z": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-5-n": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-5-n/": &testBody{404, nil}, | |||
"/custom-base-route/custom-base-route-abuse-6-o": &testBody{301, nil}, | |||
"/custom-base-route/custom-base-route-abuse-6-o/": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-7-s": &testBody{404, nil}, | |||
"/custom-base-route/custom-base-route-abuse-7-s/": &testBody{200, []byte("ABUSE")}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} | |||
func Test_MethodOverride(t *testing.T) { | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBodyMethod{ | |||
"/method-override/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override/new": &testBodyMethod{200, []byte("POST"), "POST"}, | |||
"/method-override/remove": &testBodyMethod{200, []byte("DELETE"), "DELETE"}, | |||
"/method-override/replace": &testBodyMethod{200, []byte("PUT"), "PUT"}, | |||
"/method-override/update": &testBodyMethod{200, []byte("PATCH"), "PATCH"}, | |||
"/method-override/summary": &testBodyMethod{200, []byte("HEAD"), "HEAD"}, | |||
"/method-override-no-slash/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-no-slash/read/": &testBodyMethod{404, nil, "GET"}, | |||
"/method-override-no-slash/new": &testBodyMethod{200, []byte("POST"), "POST"}, | |||
"/method-override-no-slash/new/": &testBodyMethod{404, nil, "POST"}, | |||
"/method-override-no-slash/remove": &testBodyMethod{200, []byte("DELETE"), "DELETE"}, | |||
"/method-override-no-slash/remove/": &testBodyMethod{404, nil, "DELETE"}, | |||
"/method-override-no-slash/replace": &testBodyMethod{200, []byte("PUT"), "PUT"}, | |||
"/method-override-no-slash/replace/": &testBodyMethod{404, nil, "PUT"}, | |||
"/method-override-no-slash/update": &testBodyMethod{200, []byte("PATCH"), "PATCH"}, | |||
"/method-override-no-slash/update/": &testBodyMethod{404, nil, "PATCH"}, | |||
"/method-override-no-slash/summary": &testBodyMethod{200, []byte("HEAD"), "HEAD"}, | |||
"/method-override-no-slash/summary/": &testBodyMethod{404, nil, "HEAD"}, | |||
"/method-override-slash/read": &testBodyMethod{404, nil, "GET"}, | |||
"/method-override-slash/read/": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional/read": &testBodyMethod{301, nil, "GET"}, | |||
"/method-override-optional/read/": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional-none/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional-none/read/": &testBodyMethod{301, nil, "GET"}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Method(body.Method, url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} | |||
// Test_SpecialEndpoints tests specially named endpoints: | |||
// | |||
// - Index | |||
func Test_SpecialEndpoints(t *testing.T) { | |||
} | |||
// Test_WebSocket tests the websocket endpoint. | |||
func Test_WebSocketEndpoint(t *testing.T) { | |||
} | |||
// Test_NamedEndpoints tests named suffix endpoints. These endpoints contain a | |||
// camel-to-hyphen-case converted endpoint with the method as its suffix (plus a | |||
// single character modifier indicating the trailing slash state). As an | |||
// example, a controller endpoint named ListEmailsGetN would be translated to | |||
// the endpoint name "list-emails" using the GET method and no trailing slash | |||
// ("N" suffix). | |||
// | |||
// This unit test tests naming and most common methods, plus the suffix | |||
// characters N (no trailing slash), O (optional trailing slash), and S | |||
// (mandatory trailing slash). | |||
func Test_NamedEndpoints(t *testing.T) { | |||
} |
@@ -0,0 +1,140 @@ | |||
// +build integration | |||
// Server configuration for unit testing. | |||
package tests | |||
import ( | |||
"net/http" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
type CustomBaseRouteController struct { | |||
capstan.BaseController | |||
} | |||
func (c *CustomBaseRouteController) Demo(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("DEMO")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute1(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-1")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute2N(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-N")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute3O(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-O")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute4S(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-S")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) Get_Custom_Route_Underscore(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM_UNDERSCORE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse1A(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse2a(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse3Z(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse4z(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse5NN(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse6OO(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse7SS(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func Test_CustomBaseRoutes(t *testing.T) { | |||
server := NewServer(func (app capstan.Server){ | |||
app.BindRoute(&CustomBaseRouteController{ | |||
capstan.BaseController{ | |||
Path: "/custom-base-route", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBody{ | |||
"/custom-base-route/demo": &testBody{200, []byte("DEMO")}, | |||
"/custom-base-route/custom-route-1": &testBody{200, []byte("CUSTOM-1")}, | |||
"/custom-base-route/custom-route-2": &testBody{200, []byte("CUSTOM-N")}, | |||
"/custom-base-route/custom-route-2/": &testBody{404, nil}, | |||
"/custom-base-route/custom-route-3": &testBody{301, nil}, | |||
"/custom-base-route/custom-route-3/": &testBody{200, []byte("CUSTOM-O")}, | |||
"/custom-base-route/custom-route-4": &testBody{404, nil}, | |||
"/custom-base-route/custom-route-4/": &testBody{200, []byte("CUSTOM-S")}, | |||
"/custom-base-route/custom-route-underscore": &testBody{200, []byte("CUSTOM_UNDERSCORE")}, | |||
"/custom-base-route/custom-base-route-abuse-1-a": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-2a": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-3-z": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-4z": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-5-n": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-5-n/": &testBody{404, nil}, | |||
"/custom-base-route/custom-base-route-abuse-6-o": &testBody{301, nil}, | |||
"/custom-base-route/custom-base-route-abuse-6-o/": &testBody{200, []byte("ABUSE")}, | |||
"/custom-base-route/custom-base-route-abuse-7-s": &testBody{404, nil}, | |||
"/custom-base-route/custom-base-route-abuse-7-s/": &testBody{200, []byte("ABUSE")}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
) | |||
type DependencyTestController struct { | |||
capstan.BaseController | |||
App capstan.Server `inject:"app"` | |||
Dep1 *Dep `inject:""` | |||
Dep2 *Dep `inject:"dep2"` | |||
Dep3 *Dep `inject:"dep3"` | |||
} | |||
// FailDependencyTestController attempts to inject a dependency that doesn't | |||
// exist. This should generate a panic when the endpoint is loaded. | |||
type FailDependencyTestController struct { | |||
capstan.BaseController | |||
None *Dep `inject:"does-not-exist"` | |||
} |
@@ -0,0 +1,10 @@ | |||
// +build integration | |||
// This directory contains integration tests validating the user-facing Capstan | |||
// API. | |||
// | |||
// These tests may include directives that communicate with other services or | |||
// require spinning up multiple instances of the Capstan server. Consequently, | |||
// these tests require the use of the `integration` tag. These tests will | |||
// not run unless you enable the flag or use `make test`. | |||
package tests |
@@ -0,0 +1,134 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"bytes" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
type IndexController struct { | |||
capstan.BaseController | |||
} | |||
func (c *IndexController) Index(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("INDEX")) | |||
return err | |||
} | |||
func (c *IndexController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte(ctx.Param("param"))) | |||
return err | |||
} | |||
func Test_IndexController(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index", | |||
Path: "/index/<param:string>/?", | |||
Index: "/?", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-slash", | |||
Path: "/index-slash/<param:string>/", | |||
Index: "/+", // Implicit mandatory slashes don't work here. | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-no-slash", | |||
Path: "/index-no-slash/<param:string>!", | |||
Index: "!", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-no-slash-slash", | |||
Path: "/index-no-slash-slash/<param:string>/!", | |||
Index: "/!", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-sub-index", | |||
Path: "/index-sub-index/<param:string>/!", | |||
Index: "/index", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
client := server.Client() | |||
client.CheckRedirect = nil | |||
tests := map[string]*testBody{ | |||
// Optional trailing slash ("/?"). | |||
"/index": &testBody{200, []byte("INDEX")}, | |||
"/index/": &testBody{200, []byte("INDEX")}, | |||
"/index/testing": &testBody{200, []byte("testing")}, | |||
"/index/testing/": &testBody{200, []byte("testing")}, | |||
// Mandatory slash ("/"). | |||
"/index-slash": &testBody{404, nil}, | |||
"/index-slash/": &testBody{200, []byte("INDEX")}, | |||
"/index-slash/testing": &testBody{404, nil}, | |||
"/index-slash/testing/": &testBody{200, []byte("testing")}, | |||
// Mandatory no-slash exclamation ("!"). | |||
"/index-no-slash": &testBody{200, []byte("INDEX")}, | |||
"/index-no-slash/": &testBody{404, nil}, | |||
"/index-no-slash/testing": &testBody{200, []byte("testing")}, | |||
"/index-no-slash/testing/": &testBody{404, nil}, | |||
// Mandatory no-slash exclamation plus slash ("/!"). | |||
"/index-no-slash-slash": &testBody{200, []byte("INDEX")}, | |||
"/index-no-slash-slash/": &testBody{404, nil}, | |||
"/index-no-slash-slash/testing": &testBody{200, []byte("testing")}, | |||
"/index-no-slash-slash/testing/": &testBody{404, nil}, | |||
// Test index-specific route. | |||
"/index-sub-index/index": &testBody{200, []byte("INDEX")}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
out, err := response.ReadAll() | |||
if err != nil { | |||
t.Fatalf("%s: error reading body: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
// TODO: A nil body indicates we're not going to do a complete | |||
// comparison at this time. | |||
if body.Body != nil && bytes.Compare(out, body.Body) != 0 { | |||
t.Fatalf(`%s: unexpected body: "%v"`, url, | |||
string(out)) | |||
} | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
// This is the unit test package for Capstan. It provides a high level series of | |||
// usage tests that exercise its exposed functionality as per endpoints and | |||
// their respective configurations. Consequently, this test may be used in | |||
// addition to Capstan's documentation as each test is segregated from the | |||
// others and performed (mostly) in isolation. A single server instance is spun | |||
// up to run internally, but each test has its own endpoint. | |||
// | |||
// Other packages provide more fine-grained unit tests to validate correctness; | |||
// this package should be considered a test of the public API. | |||
package tests | |||
import ( | |||
"os" | |||
"testing" | |||
) | |||
func TestMain(m *testing.M) { | |||
os.Exit(m.Run()) | |||
} |
@@ -0,0 +1,107 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"bytes" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
type MethodEndpointController struct { | |||
capstan.BaseController | |||
} | |||
func (m *MethodEndpointController) Get(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "GET") | |||
ctx.Write([]byte("GET")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Post(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "POST") | |||
ctx.Write([]byte("POST")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Put(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "PUT") | |||
ctx.Write([]byte("PUT")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Patch(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "PATCH") | |||
ctx.Write([]byte("PATCH")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Delete(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "DELETE") | |||
ctx.Write([]byte("DELETE")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Head(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "HEAD") | |||
return nil | |||
} | |||
// Test_MethodEndpoints tests all method endpoints (see controller above): | |||
// | |||
// - Get | |||
// - Head | |||
// - Post | |||
// - Put | |||
// - Patch | |||
// - Delete | |||
// - Options | |||
// - Trace | |||
// - Connect | |||
func Test_MethodEndpoints(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&MethodEndpointController{ | |||
capstan.BaseController{ | |||
Path: "/methods", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
var out []byte | |||
client := server.Client() | |||
client.CheckRedirect = nil | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(server.Client()) | |||
d := map[string]*testMethods{ | |||
"GET": &testMethods{true, requester.Get}, | |||
"POST": &testMethods{true, requester.Post}, | |||
"PUT": &testMethods{true, requester.Put}, | |||
"PATCH": &testMethods{true, requester.Patch}, | |||
"DELETE": &testMethods{true, requester.Delete}, | |||
"HEAD": &testMethods{false, requester.Head}, | |||
} | |||
for k, v := range d { | |||
req := v.Method("/methods") | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s response error: %v", k, errors.Unfurl(err)) | |||
} | |||
out, err = response.ReadAll() | |||
if err != nil { | |||
t.Fatalf("%s read body error: %v", k, err) | |||
} | |||
if v.HasBody && bytes.Compare(out, []byte(k)) != 0 { | |||
t.Errorf("%s returned unexpected body: %v", k, string(out)) | |||
} | |||
if response.Headers().Get("X-Test-Method") != k { | |||
t.Errorf("%s did not return correct X-Test-Method header", k) | |||
} | |||
} | |||
} |
@@ -0,0 +1,160 @@ | |||
// +build integration | |||
// Server configuration for unit testing. | |||
package tests | |||
import ( | |||
"net/http" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
type MethodOverrideController struct { | |||
capstan.BaseController | |||
} | |||
func (c *MethodOverrideController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("GET")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Post(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("POST")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Delete(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("DELETE")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Put(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("PUT")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Patch(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("PATCH")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Head(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("HEAD")) | |||
return err | |||
} | |||
func Test_MethodOverride(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override", | |||
Get: "/read", | |||
Post: "/new", | |||
Put: "/replace", | |||
Patch: "/update", | |||
Delete: "/remove", | |||
Head: "/summary", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-no-slash", | |||
Get: "/read/!", | |||
Post: "/new/!", | |||
Put: "/replace/!", | |||
Patch: "/update/!", | |||
Delete: "/remove/!", | |||
Head: "/summary/!", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-slash", | |||
Get: "/read/", | |||
Post: "/new/", | |||
Put: "/replace/", | |||
Patch: "/update/", | |||
Delete: "/remove/", | |||
Head: "/summary/", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-optional", | |||
Get: "/read/?", | |||
Head: "/summary/?", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-optional-none", | |||
Get: "/read?", | |||
Head: "/summary?", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBodyMethod{ | |||
"/method-override/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override/new": &testBodyMethod{200, []byte("POST"), "POST"}, | |||
"/method-override/remove": &testBodyMethod{200, []byte("DELETE"), "DELETE"}, | |||
"/method-override/replace": &testBodyMethod{200, []byte("PUT"), "PUT"}, | |||
"/method-override/update": &testBodyMethod{200, []byte("PATCH"), "PATCH"}, | |||
"/method-override/summary": &testBodyMethod{200, []byte("HEAD"), "HEAD"}, | |||
"/method-override-no-slash/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-no-slash/read/": &testBodyMethod{404, nil, "GET"}, | |||
"/method-override-no-slash/new": &testBodyMethod{200, []byte("POST"), "POST"}, | |||
"/method-override-no-slash/new/": &testBodyMethod{404, nil, "POST"}, | |||
"/method-override-no-slash/remove": &testBodyMethod{200, []byte("DELETE"), "DELETE"}, | |||
"/method-override-no-slash/remove/": &testBodyMethod{404, nil, "DELETE"}, | |||
"/method-override-no-slash/replace": &testBodyMethod{200, []byte("PUT"), "PUT"}, | |||
"/method-override-no-slash/replace/": &testBodyMethod{404, nil, "PUT"}, | |||
"/method-override-no-slash/update": &testBodyMethod{200, []byte("PATCH"), "PATCH"}, | |||
"/method-override-no-slash/update/": &testBodyMethod{404, nil, "PATCH"}, | |||
"/method-override-no-slash/summary": &testBodyMethod{200, []byte("HEAD"), "HEAD"}, | |||
"/method-override-no-slash/summary/": &testBodyMethod{404, nil, "HEAD"}, | |||
"/method-override-slash/read": &testBodyMethod{404, nil, "GET"}, | |||
"/method-override-slash/read/": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional/read": &testBodyMethod{301, nil, "GET"}, | |||
"/method-override-optional/read/": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional-none/read": &testBodyMethod{200, []byte("GET"), "GET"}, | |||
"/method-override-optional-none/read/": &testBodyMethod{301, nil, "GET"}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Method(body.Method, url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} |
@@ -0,0 +1,80 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"net/http" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
type ParamsController struct { | |||
capstan.BaseController | |||
} | |||
func (c *ParamsController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte(ctx.Param("from"))) | |||
return err | |||
} | |||
func Test_ParameterizedRedirects(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Path: "/params/<from:string>!", | |||
}, | |||
}) | |||
// Used to test optional trailing slash with a default redirect preference | |||
// terminating with a slash. | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Name: "parameterized-redirects-slash", | |||
Path: "/param-redirect-slash/<from:string>/?", | |||
}, | |||
}) | |||
// Used to test optional trailing slash with a default redirect preference | |||
// terminating without a slash. | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Name: "parameterized-redirects-no-slash", | |||
Path: "/param-redirect-no-slash/<from:string>?", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
tests := map[string]*testBody{ | |||
"/param-redirect-slash/testing1": &testBody{301, nil}, | |||
"/param-redirect-slash/testing1/": &testBody{200, []byte("testing1")}, | |||
"/param-redirect-no-slash/testing2": &testBody{200, []byte("testing2")}, | |||
"/param-redirect-no-slash/testing2/": &testBody{301, nil}, | |||
} | |||
for url, body := range tests { | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
// Test index response. | |||
req := requester.Get(url) | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("%s: response error during redirect: %v", url, | |||
errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != body.Code { | |||
t.Fatalf("%s: received non-%d status code: %d", url, | |||
body.Code, response.StatusCode) | |||
} | |||
} | |||
} |
@@ -0,0 +1,104 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"net/http" | |||
"net/url" | |||
"strings" | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/capstan/status" | |||
"git.pluggableideas.com/destrealm/go/please" | |||
"gitlab.com/destrealm/go/errors" | |||
) | |||
// ExternalRedirectController tests redirections, both local and external. In | |||
// this case, we simply use MethodEndpointController:get. | |||
type ExternalRedirectController struct { | |||
capstan.BaseController | |||
} | |||
func (c *ExternalRedirectController) Get(ctx capstan.Context) error { | |||
url := ctx.URLFor("MethodEndpointController:get") | |||
url.External = true | |||
err := status.Redirect(url.Encode(), 301) | |||
return err | |||
} | |||
type InternalRedirectController struct { | |||
capstan.BaseController | |||
} | |||
func (c *InternalRedirectController) Get(ctx capstan.Context) error { | |||
return status.Internal("MethodEndpointController:get", | |||
url.Values{"from": []string{"redirect"}}) | |||
} | |||
func Test_Redirects(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&ExternalRedirectController{ | |||
capstan.BaseController{ | |||
Path: "/redirect/external", | |||
}, | |||
}) | |||
app.BindRoute(&InternalRedirectController{ | |||
capstan.BaseController{ | |||
Path: "/redirect/internal", | |||
}, | |||
}) | |||
app.BindRoute(&MethodEndpointController{ | |||
capstan.BaseController{ | |||
Path: "/methods", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
client := server.Client() | |||
client.CheckRedirect = func(req *http.Request, via []*http.Request) error { | |||
return http.ErrUseLastResponse | |||
} | |||
requester := please.MakeRequest(server.URL) | |||
requester.SetClient(client) | |||
req := requester.Get("/redirect/external") | |||
response, err := req.Commit() | |||
if err != nil { | |||
t.Fatalf("response error during redirect: %v", errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != 301 { | |||
t.Fatalf("received non-redirect status code %d", response.StatusCode) | |||
} | |||
if h := response.Headers().Get("location"); h == "" { | |||
t.Fatal("expected location header missing") | |||
} else { | |||
if !strings.Contains(h, server.URL+"/methods") { | |||
t.Fatalf("unexpected redirection path: %s", h) | |||
} | |||
} | |||
req = requester.Get("/redirect/internal") | |||
response, err = req.Commit() | |||
if err != nil { | |||
t.Fatalf("response error during redirect: %v", errors.Unfurl(err)) | |||
} | |||
if response.StatusCode != 301 { | |||
t.Fatalf("received non-redirect status code %d", response.StatusCode) | |||
} | |||
if h := response.Headers().Get("location"); h == "" { | |||
t.Fatal("expected location header missing") | |||
} else { | |||
if !strings.Contains(h, "/methods") { | |||
t.Fatalf("unexpected redirection path: %s", h) | |||
} | |||
} | |||
} |
@@ -0,0 +1,22 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"testing" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
) | |||
func Test_ReturnTypes(t *testing.T) { | |||
server := NewServer(func(app capstan.Server) { | |||
app.BindRoute(&ReturnTypesController{ | |||
capstan.BaseController{ | |||
Path: "/return", | |||
}, | |||
}) | |||
}) | |||
defer server.Close() | |||
} |
@@ -3,219 +3,14 @@ | |||
package tests | |||
import ( | |||
"net/http" | |||
"net/http/httptest" | |||
"net/url" | |||
"git.pluggableideas.com/destrealm/go/capstan" | |||
"git.pluggableideas.com/destrealm/go/capstan/status" | |||
"git.pluggableideas.com/destrealm/go/capstan/config" | |||
) | |||
type Dep struct { | |||
Value1 string | |||
Value2 int | |||
} | |||
type DependencyTestController struct { | |||
capstan.BaseController | |||
App capstan.Server `inject:"app"` | |||
Dep1 *Dep `inject:""` | |||
Dep2 *Dep `inject:"dep2"` | |||
Dep3 *Dep `inject:"dep3"` | |||
} | |||
// FailDependencyTestController attempts to inject a dependency that doesn't | |||
// exist. This should generate a panic when the endpoint is loaded. | |||
type FailDependencyTestController struct { | |||
capstan.BaseController | |||
None *Dep `inject:"does-not-exist"` | |||
} | |||
type MethodEndpointController struct { | |||
capstan.BaseController | |||
} | |||
func (m *MethodEndpointController) Get(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "GET") | |||
ctx.Write([]byte("GET")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Post(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "POST") | |||
ctx.Write([]byte("POST")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Put(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "PUT") | |||
ctx.Write([]byte("PUT")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Patch(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "PATCH") | |||
ctx.Write([]byte("PATCH")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Delete(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "DELETE") | |||
ctx.Write([]byte("DELETE")) | |||
return nil | |||
} | |||
func (m *MethodEndpointController) Head(ctx capstan.Context) error { | |||
ctx.Response().Header().Add("X-Test-Method", "HEAD") | |||
return nil | |||
} | |||
type IndexController struct { | |||
capstan.BaseController | |||
} | |||
func (c *IndexController) Index(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("INDEX")) | |||
return err | |||
} | |||
func (c *IndexController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte(ctx.Param("param"))) | |||
return err | |||
} | |||
type ParamsController struct { | |||
capstan.BaseController | |||
} | |||
func (c *ParamsController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte(ctx.Param("from"))) | |||
return err | |||
} | |||
type CustomBaseRouteController struct { | |||
capstan.BaseController | |||
} | |||
func (c *CustomBaseRouteController) Demo(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("DEMO")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute1(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-1")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute2N(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-N")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute3O(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-O")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomRoute4S(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM-S")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) Get_Custom_Route_Underscore(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("CUSTOM_UNDERSCORE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse1A(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse2a(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse3Z(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse4z(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse5NN(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse6OO(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
func (c *CustomBaseRouteController) GetCustomBaseRouteAbuse7SS(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("ABUSE")) | |||
return err | |||
} | |||
type MethodOverrideController struct { | |||
capstan.BaseController | |||
} | |||
func (c *MethodOverrideController) Get(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("GET")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Post(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("POST")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Delete(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("DELETE")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Put(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("PUT")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Patch(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("PATCH")) | |||
return err | |||
} | |||
func (c *MethodOverrideController) Head(ctx capstan.Context) error { | |||
_, err := ctx.Write([]byte("HEAD")) | |||
return err | |||
} | |||
// ExternalRedirectController tests redirections, both local and external. In | |||
// this case, we simply use MethodEndpointController:get. | |||
type ExternalRedirectController struct { | |||
capstan.BaseController | |||
} | |||
func (c *ExternalRedirectController) Get(ctx capstan.Context) error { | |||
url := ctx.URLFor("MethodEndpointController:get") | |||
url.External = true | |||
err := status.Redirect(url.Encode(), 301) | |||
return err | |||
} | |||
type InternalRedirectController struct { | |||
capstan.BaseController | |||
} | |||
func (c *InternalRedirectController) Get(ctx capstan.Context) error { | |||
return status.Internal("MethodEndpointController:get", | |||
url.Values{"from": []string{"redirect"}}) | |||
} | |||
// ReturnTypesController tests various return types, including direct `error` | |||
// types, capstan.Context as an error return type, etc. | |||
@@ -223,153 +18,50 @@ type ReturnTypesController struct { | |||
capstan.BaseController | |||
} | |||
func Server() *httptest.Server { | |||
app := capstan.NewServer() | |||
app.BindRoute(&MethodEndpointController{ | |||
capstan.BaseController{ | |||
Path: "/methods", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index", | |||
Path: "/index/<param:string>/?", | |||
Index: "/?", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-slash", | |||
Path: "/index-slash/<param:string>/", | |||
Index: "/+", // Implicit mandatory slashes don't work here. | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-no-slash", | |||
Path: "/index-no-slash/<param:string>!", | |||
Index: "!", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-no-slash-slash", | |||
Path: "/index-no-slash-slash/<param:string>/!", | |||
Index: "/!", | |||
}, | |||
}) | |||
app.BindRoute(&IndexController{ | |||
capstan.BaseController{ | |||
Name: "index-sub-index", | |||
Path: "/index-sub-index/<param:string>/!", | |||
Index: "/index", | |||
}, | |||
}) | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Path: "/params/<from:string>!", | |||
}, | |||
}) | |||
// Used to test optional trailing slash with a default redirect preference | |||
// terminating with a slash. | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Name: "parameterized-redirects-slash", | |||
Path: "/param-redirect-slash/<from:string>/?", | |||
}, | |||
}) | |||
// Used to test optional trailing slash with a default redirect preference | |||
// terminating without a slash. | |||
app.BindRoute(&ParamsController{ | |||
capstan.BaseController{ | |||
Name: "parameterized-redirects-no-slash", | |||
Path: "/param-redirect-no-slash/<from:string>?", | |||
}, | |||
}) | |||
app.BindRoute(&CustomBaseRouteController{ | |||
capstan.BaseController{ | |||
Path: "/custom-base-route", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override", | |||
Get: "/read", | |||
Post: "/new", | |||
Put: "/replace", | |||
Patch: "/update", | |||
Delete: "/remove", | |||
Head: "/summary", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-no-slash", | |||
Get: "/read/!", | |||
Post: "/new/!", | |||
Put: "/replace/!", | |||
Patch: "/update/!", | |||
Delete: "/remove/!", | |||
Head: "/summary/!", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-slash", | |||
Get: "/read/", | |||
Post: "/new/", | |||
Put: "/replace/", | |||
Patch: "/update/", | |||
Delete: "/remove/", | |||
Head: "/summary/", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-optional", | |||
Get: "/read/?", | |||
Head: "/summary/?", | |||
}, | |||
}) | |||
app.BindRoute(&MethodOverrideController{ | |||
capstan.BaseController{ | |||
Path: "/method-override-optional-none", | |||
Get: "/read?", | |||
Head: "/summary?", | |||
}, | |||
}) | |||
app.BindRoute(&ExternalRedirectController{ | |||
capstan.BaseController{ | |||
Path: "/redirect/external", | |||
}, | |||
}) | |||
app.BindRoute(&InternalRedirectController{ | |||
capstan.BaseController{ | |||
Path: "/redirect/internal", | |||
}, | |||
}) | |||
app.BindRoute(&ReturnTypesController{ | |||
capstan.BaseController{ | |||
Path: "/return", | |||
}, | |||
}) | |||
func NewServer(bind func(capstan.Server)) *httptest.Server { | |||
app := capstan.NewServer(&capstan.ServerConfig{ | |||
Session: &capstan.SessionConfig{ | |||
Backend: config.SessionCookieBackend, | |||
PlainText: true, | |||
CookieOptions: &http.Cookie{ | |||
Name: "test_session", | |||
}, | |||
// Key data from KeyStar crypto/init_test.go. Do not re-use; this is | |||
// public. | |||
Key: []byte{ | |||
0x45, 0x5c, 0x9a, 0x7c, | |||
0xa1, 0x3e, 0x33, 0x70, | |||
0x23, 0x35, 0x3b, 0x71, | |||
0xed, 0xd3, 0xe0, 0x14, | |||
0xf0, 0x8e, 0x45, 0x7f, | |||
0xcf, 0x2b, 0x9f, 0xa6, | |||
0x66, 0xbd, 0xc1, 0x43, | |||
0xae, 0x08, 0x49, 0x4c, | |||
}, | |||
// HMAC key from KeyStar crypto/init_test.go. Do not reuse; this is | |||
// public. | |||
HMACKey: []byte{ | |||
0x93, 0x31, 0x67, 0x11, | |||
0xf4, 0xec, 0x18, 0x45, | |||
0xd6, 0xdf, 0x5f, 0x86, | |||
0x06, 0x74, 0x44, 0xf9, | |||
0x3c, 0xe5, 0xc8, 0x16, | |||
0x79, 0x2b, 0x46, 0xa6, | |||
0x9b, 0x7d, 0x06, 0x66, | |||
0x9d, 0xda, 0x90, 0xb9, | |||
0x7e, 0x45, 0xfa, 0x7a, | |||
0x69, 0xe8, 0x5c, 0x1a, | |||
0xb3, 0xd4, 0xab, 0x66, | |||
0x3e, 0x47, 0xc7, 0x02, | |||
0x87, 0x79, 0x89, 0xcf, | |||
0x2c, 0x21, 0x2f, 0xb2, | |||
0x5e, 0xc9, 0xa9, 0x8c, | |||
0xa9, 0xa8, 0xa8, 0xdd, | |||
}, | |||
}, | |||
}) | |||
bind(app) | |||
server := httptest.NewServer(app.Router().Mux()) | |||
parsed, _ := url.Parse(server.URL) | |||
@@ -0,0 +1,33 @@ | |||
// +build integration | |||
package tests | |||
import ( | |||
"testing" | |||
) | |||
// Test_SpecialEndpoints tests specially named endpoints: | |||
// | |||
// - Index | |||
func Test_SpecialEndpoints(t *testing.T) { | |||
} | |||
// Test_WebSocket tests the websocket endpoint. | |||
func Test_WebSocketEndpoint(t *testing.T) { | |||
} | |||
// Test_NamedEndpoints tests named suffix endpoints. These endpoints contain a | |||
// camel-to-hyphen-case converted endpoint with the method as its suffix (plus a | |||
// single character modifier indicating the trailing slash state). As an | |||
// example, a controller endpoint named ListEmailsGetN would be translated to | |||
// the endpoint name "list-emails" using the GET method and no trailing slash | |||
// ("N" suffix). | |||
// | |||
// This unit test tests naming and most common methods, plus the suffix | |||
// characters N (no trailing slash), O (optional trailing slash), and S | |||
// (mandatory trailing slash). | |||
func Test_NamedEndpoints(t *testing.T) { | |||
} |
@@ -0,0 +1,26 @@ | |||
package tests | |||
import ( | |||
"git.pluggableideas.com/destrealm/go/please" | |||
) | |||
type Dep struct { | |||
Value1 string | |||
Value2 int | |||
} | |||
type testMethods struct { | |||
HasBody bool | |||
Method func(string) *please.ClientRequest | |||
} | |||
type testBody struct { | |||
Code int | |||
Body []byte | |||
} | |||
type testBodyMethod struct { | |||
Code int | |||
Body []byte | |||
Method string | |||
} |