summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-01-11 11:41:07 -0700
committertjpcc <tjp@ctrl-c.club>2023-01-11 11:41:07 -0700
commit4969e33e28e09581a3b380dec7ebdc8594d67838 (patch)
tree3fe162f05aad5df87d64f3117ab3af2e433fab41
parente183f9cd23380a81071c32f64c91e60f46a7d8cb (diff)
WIP improve test coverage
There is a test of Response.Read that is failing and I haven't yet figured out why.
-rw-r--r--.gitignore1
-rw-r--r--gemini/request_test.go17
-rw-r--r--gemini/response_test.go166
3 files changed, 184 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2d83068
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+coverage.out
diff --git a/gemini/request_test.go b/gemini/request_test.go
index 1da24f7..c23d54b 100644
--- a/gemini/request_test.go
+++ b/gemini/request_test.go
@@ -3,6 +3,7 @@ package gemini_test
import (
"bytes"
"testing"
+ "net/url"
"tildegit.org/tjp/gus/gemini"
)
@@ -84,3 +85,19 @@ func TestParseRequest(t *testing.T) {
})
}
}
+
+func TestUnescapedQuery(t *testing.T) {
+ table := []string{
+ "foo bar",
+ }
+
+ for _, test := range table {
+ t.Run(test, func(t *testing.T) {
+ u, _ := url.Parse("gemini://domain.com/path?" + url.QueryEscape(test))
+ result := gemini.Request{ URL: u }.UnescapedQuery()
+ if result != test {
+ t.Errorf("expected %q, got %q", test, result)
+ }
+ })
+ }
+}
diff --git a/gemini/response_test.go b/gemini/response_test.go
index 3e1f41f..7ffb585 100644
--- a/gemini/response_test.go
+++ b/gemini/response_test.go
@@ -149,3 +149,169 @@ func TestBuildResponses(t *testing.T) {
})
}
}
+
+func TestParseResponses(t *testing.T) {
+ table := []struct {
+ input string
+ status gemini.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 := &gemini.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 = &gemini.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 *gemini.Response) *gemini.Response {
+ other := &gemini.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 resp
+ }
+
+ r1 := &gemini.Response{
+ Status: gemini.StatusSuccess,
+ Meta: "text/gemini",
+ Body: bytes.NewBufferString("the body goes here"),
+ }
+ r2 := clone(r1)
+
+ wtbuf := &bytes.Buffer{}
+ if _, err := r1.WriteTo(wtbuf); err != nil {
+ t.Fatalf("response.WriteTo(): %s", err.Error())
+ }
+
+ rdbuf := make([]byte, wtbuf.Len())
+ if n, err := r2.Read(rdbuf); err != nil {
+ t.Fatalf("response.Read() -> %d: %s", n, err.Error())
+ }
+
+ if wtbuf.String() != string(rdbuf) {
+ t.Fatalf("Read produced %q but WriteTo produced %q", string(rdbuf), wtbuf.String())
+ }
+}