diff --git a/mux_test.go b/mux_test.go index 2a2101f..7abc487 100644 --- a/mux_test.go +++ b/mux_test.go @@ -2,6 +2,7 @@ package gemini import ( "context" + "io" "net/url" "testing" ) @@ -10,6 +11,221 @@ type nopHandler struct{} func (*nopHandler) ServeGemini(context.Context, ResponseWriter, *Request) {} +type nopResponseWriter struct { + Status Status + Meta string +} + +func (w *nopResponseWriter) WriteHeader(status Status, meta string) { + w.Status = status + w.Meta = meta +} + +func (nopResponseWriter) SetMediaType(mediatype string) {} +func (nopResponseWriter) Write(b []byte) (int, error) { return 0, io.EOF } +func (nopResponseWriter) Flush() error { return nil } + +func TestMux(t *testing.T) { + type Test struct { + URL string + Pattern string + Redirect string + NotFound bool + } + + tests := []struct { + Patterns []string + Tests []Test + }{ + { + Patterns: []string{"/a", "/b/", "/b/c/d", "/b/c/d/"}, + Tests: []Test{ + { + URL: "gemini://example.com", + Redirect: "gemini://example.com/", + }, + { + URL: "gemini://example.com/", + NotFound: true, + }, + { + URL: "gemini://example.com/c", + NotFound: true, + }, + { + URL: "gemini://example.com/a", + Pattern: "/a", + }, + { + URL: "gemini://example.com/a/", + NotFound: true, + }, + { + URL: "gemini://example.com/b", + Redirect: "gemini://example.com/b/", + }, + { + URL: "gemini://example.com/b/", + Pattern: "/b/", + }, + { + URL: "gemini://example.com/b/c", + Pattern: "/b/", + }, + { + URL: "gemini://example.com/b/c/d", + Pattern: "/b/c/d", + }, + { + URL: "gemini://example.com/b/c/d/e/", + Pattern: "/b/c/d/", + }, + }, + }, + { + Patterns: []string{ + "/", "/a", "/b/", + "example.com", "example.com/a", "example.com/b/", + "*.example.com", "*.example.com/a", "*.example.com/b/", + }, + Tests: []Test{ + { + URL: "gemini://example.net/", + Pattern: "/", + }, + { + URL: "gemini://example.net/a", + Pattern: "/a", + }, + { + URL: "gemini://example.net/b", + Redirect: "gemini://example.net/b/", + }, + { + URL: "gemini://example.net/b/", + Pattern: "/b/", + }, + { + URL: "gemini://example.com/", + Pattern: "example.com", + }, + { + URL: "gemini://example.com/b", + Redirect: "gemini://example.com/b/", + }, + { + URL: "gemini://example.com/b/", + Pattern: "example.com/b/", + }, + { + URL: "gemini://a.example.com/", + Pattern: "*.example.com", + }, + { + URL: "gemini://b.example.com/a", + Pattern: "*.example.com/a", + }, + { + URL: "gemini://c.example.com/b", + Redirect: "gemini://c.example.com/b/", + }, + { + URL: "gemini://d.example.com/b/", + Pattern: "*.example.com/b/", + }, + }, + }, + { + Patterns: []string{"example.net", "*.example.org"}, + Tests: []Test{ + { + // The following redirect occurs as a result of cleaning + // the path provided to the Mux. This happens even if there + // are no matching handlers. + URL: "gemini://example.com", + Redirect: "gemini://example.com/", + }, + { + URL: "gemini://example.com/", + NotFound: true, + }, + { + URL: "gemini://example.net", + Redirect: "gemini://example.net/", + }, + { + URL: "gemini://example.org/", + NotFound: true, + }, + { + URL: "gemini://gemini.example.org", + Redirect: "gemini://gemini.example.org/", + }, + }, + }, + } + + for _, test := range tests { + type handler struct { + nopHandler + Pattern string + } + + mux := &Mux{} + for _, pattern := range test.Patterns { + mux.Handle(pattern, &handler{ + Pattern: pattern, + }) + } + + for _, test := range test.Tests { + u, err := url.Parse(test.URL) + if err != nil { + panic(err) + } + + req := &Request{URL: u} + + h := mux.Handler(req) + + if h, ok := h.(*handler); ok { + if h.Pattern != test.Pattern { + t.Errorf("wrong pattern for %q: expected %q, got %q", test.URL, test.Pattern, h.Pattern) + } + continue + } + + // Check redirects and NotFounds + w := &nopResponseWriter{} + h.ServeGemini(context.Background(), w, req) + + switch w.Status { + case StatusNotFound: + if !test.NotFound { + t.Errorf("expected pattern for %q, got NotFound", test.URL) + } + + case StatusPermanentRedirect: + if test.Redirect == "" { + t.Errorf("expected pattern for %q, got redirect to %q", test.URL, w.Meta) + break + } + + res, err := url.Parse(test.Redirect) + if err != nil { + panic(err) + } + if w.Meta != res.String() { + t.Errorf("bad redirect for %q: expected %q, got %q", test.URL, res.String(), w.Meta) + } + + default: + t.Errorf("unexpected response for %q: %d %s", test.URL, w.Status, w.Meta) + } + } + } +} + func TestMuxMatch(t *testing.T) { type Match struct { URL string @@ -115,12 +331,12 @@ func TestMuxMatch(t *testing.T) { }, } - for i, test := range tests { + for _, test := range tests { h := &nopHandler{} var mux Mux mux.Handle(test.Pattern, h) - for _, match := range tests[i].Matches { + for _, match := range test.Matches { u, err := url.Parse(match.URL) if err != nil { panic(err)