This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
gus/gemini/response_test.go

336 lines
7.8 KiB
Go

package gemini_test
import (
"bytes"
"errors"
"io"
"testing"
"tildegit.org/tjp/gus"
"tildegit.org/tjp/gus/gemini"
)
func TestBuildResponses(t *testing.T) {
table := []struct {
name string
response *gus.Response
status gus.Status
meta string
body string
}{
{
name: "input response",
response: gemini.Input("prompt here"),
status: gemini.StatusInput,
meta: "prompt here",
},
{
name: "sensitive input response",
response: gemini.SensitiveInput("password please"),
status: gemini.StatusSensitiveInput,
meta: "password please",
},
{
name: "success response",
response: gemini.Success("text/gemini", bytes.NewBufferString("body text here")),
status: gemini.StatusSuccess,
meta: "text/gemini",
body: "body text here",
},
{
name: "temporary redirect",
response: gemini.Redirect("/foo/bar"),
status: gemini.StatusTemporaryRedirect,
meta: "/foo/bar",
},
{
name: "permanent redirect",
response: gemini.PermanentRedirect("/baz/qux"),
status: gemini.StatusPermanentRedirect,
meta: "/baz/qux",
},
{
name: "fail response",
response: gemini.Failure(errors.New("a failure")),
status: gemini.StatusTemporaryFailure,
meta: "a failure",
},
{
name: "server unavailable",
response: gemini.Unavailable("server unavailable"),
status: gemini.StatusServerUnavailable,
meta: "server unavailable",
},
{
name: "cgi error",
response: gemini.CGIError("some cgi error msg"),
status: gemini.StatusCGIError,
meta: "some cgi error msg",
},
{
name: "proxy error",
response: gemini.ProxyError("upstream's full"),
status: gemini.StatusProxyError,
meta: "upstream's full",
},
{
name: "rate limiting",
response: gemini.SlowDown(15),
status: gemini.StatusSlowDown,
meta: "15",
},
{
name: "permanent failure",
response: gemini.PermanentFailure(errors.New("wut r u doin")),
status: gemini.StatusPermanentFailure,
meta: "wut r u doin",
},
{
name: "not found",
response: gemini.NotFound("nope"),
status: gemini.StatusNotFound,
meta: "nope",
},
{
name: "gone",
response: gemini.Gone("all out of that"),
status: gemini.StatusGone,
meta: "all out of that",
},
{
name: "refuse proxy",
response: gemini.RefuseProxy("no I don't think I will"),
status: gemini.StatusProxyRequestRefused,
meta: "no I don't think I will",
},
{
name: "bad request",
response: gemini.BadRequest("that don't make no sense"),
status: gemini.StatusBadRequest,
meta: "that don't make no sense",
},
{
name: "require cert",
response: gemini.RequireCert("cert required"),
status: gemini.StatusClientCertificateRequired,
meta: "cert required",
},
{
name: "cert auth failure",
response: gemini.CertAuthFailure("you can't see that"),
status: gemini.StatusCertificateNotAuthorized,
meta: "you can't see that",
},
{
name: "invalid cert",
response: gemini.CertInvalid("bad cert dude"),
status: gemini.StatusCertificateNotValid,
meta: "bad cert dude",
},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
if test.response.Status != test.status {
t.Errorf("expected status %d, got %d", test.status, test.response.Status)
}
if test.response.Meta != test.meta {
t.Errorf("expected meta %q, got %q", test.meta, test.response.Meta)
}
responseBytes, err := io.ReadAll(gemini.NewResponseReader(test.response))
if err != nil {
t.Fatalf("error reading response body: %q", err.Error())
}
body := string(bytes.SplitN(responseBytes, []byte("\r\n"), 2)[1])
if body != test.body {
t.Errorf("expected body %q, got %q", test.body, body)
}
})
}
}
func TestParseResponses(t *testing.T) {
table := []struct {
input string
status gus.Status
meta string
body string
err error
}{
{
input: "20 text/gemini\r\n# you got me!\n",
status: gemini.StatusSuccess,
meta: "text/gemini",
body: "# you got me!\n",
},
{
input: "30 gemini://some.where/else\r\n",
status: gemini.StatusTemporaryRedirect,
meta: "gemini://some.where/else",
},
{
input: "10 forgot the line ending",
err: gemini.InvalidResponseLineEnding,
},
{
input: "10 wrong line ending\n",
err: gemini.InvalidResponseLineEnding,
},
{
input: "10no space\r\n",
err: gemini.InvalidResponseHeaderLine,
},
{
input: "no status code\r\n",
err: gemini.InvalidResponseHeaderLine,
},
{
input: "31 gemini://domain.com/my/new/home\r\n",
status: gemini.StatusPermanentRedirect,
meta: "gemini://domain.com/my/new/home",
},
}
for _, test := range table {
t.Run(test.input, func(t *testing.T) {
response, err := gemini.ParseResponse(bytes.NewBufferString(test.input))
if !errors.Is(err, test.err) {
t.Fatalf("expected error %s, got %s", test.err, err)
}
if err != nil {
return
}
if response.Status != test.status {
t.Errorf("expected status %d, got %d", test.status, response.Status)
}
if response.Meta != test.meta {
t.Errorf("expected meta %q, got %q", test.meta, response.Meta)
}
if response.Body == nil {
if test.body != "" {
t.Errorf("expected body %q, got nil", test.body)
}
} else {
body, err := io.ReadAll(response.Body)
if err != nil {
t.Fatalf("error reading response body: %s", err.Error())
}
if test.body != string(body) {
t.Errorf("expected body %q, got %q", test.body, string(body))
}
}
})
}
}
func TestResponseClose(t *testing.T) {
body := &rdCloser{Buffer: bytes.NewBufferString("the body here")}
resp := &gus.Response{
Status: gemini.StatusSuccess,
Meta: "text/gemini",
Body: body,
}
if err := resp.Close(); err != nil {
t.Fatalf("response close error: %s", err.Error())
}
if !body.closed {
t.Error("response body was not closed by response.Close()")
}
resp = &gus.Response{
Status: gemini.StatusInput,
Meta: "give me more",
}
if err := resp.Close(); err != nil {
t.Fatalf("response close error: %s", err.Error())
}
}
type rdCloser struct {
*bytes.Buffer
closed bool
}
func (rc *rdCloser) Close() error {
rc.closed = true
return nil
}
func TestResponseWriteTo(t *testing.T) {
// invariant under test: WriteTo() sends the same bytes as Read()
clone := func(resp *gus.Response) *gus.Response {
other := &gus.Response{
Status: resp.Status,
Meta: resp.Meta,
}
if resp.Body != nil {
// the body could be one-time readable, so replace it with a buffer
buf, err := io.ReadAll(resp.Body)
if err != nil {
t.Fatalf("error reading response body: %s", err.Error())
}
resp.Body = bytes.NewBuffer(buf)
buf2 := make([]byte, len(buf))
if copy(buf2, buf) != len(buf) {
t.Fatalf("short copy on a []byte")
}
other.Body = bytes.NewBuffer(buf2)
}
return other
}
table := []struct {
name string
response *gus.Response
}{
{
name: "simple success",
response: gemini.Success(
"text/gemini",
bytes.NewBufferString("the body goes here"),
),
},
{
name: "no body",
response: gemini.Input("need more pls"),
},
}
for _, test := range table {
t.Run(test.name, func(t *testing.T) {
r1 := test.response
r2 := clone(test.response)
rdbuf, err := io.ReadAll(gemini.NewResponseReader(r1))
if err != nil {
t.Fatalf("response.Read(): %s", err.Error())
}
wtbuf := &bytes.Buffer{}
if _, err := gemini.NewResponseReader(r2).WriteTo(wtbuf); err != nil {
t.Fatalf("response.WriteTo(): %s", err.Error())
}
if wtbuf.String() != string(rdbuf) {
t.Fatalf("Read produced %q but WriteTo produced %q", string(rdbuf), wtbuf.String())
}
})
}
}