much more extensive gus/gemini testing
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
4969e33e28
commit
029cd5b52d
|
@ -14,7 +14,7 @@ import (
|
|||
// The only reason you might create more than one Client is to support separate TLS-cert
|
||||
// driven identities.
|
||||
//
|
||||
// The zero value of Client is usable, it simply has no client TLS cert.
|
||||
// The zero value is a usable Client with no client TLS certificate.
|
||||
type Client struct {
|
||||
tlsConf *tls.Config
|
||||
}
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package gemini_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
func TestRoundTrip(t *testing.T) {
|
||||
tlsConf, err := gemini.FileTLS("./testdata/server.crt", "./testdata/server.key")
|
||||
if err != nil {
|
||||
t.Fatalf("FileTLS(): %s", err.Error())
|
||||
}
|
||||
|
||||
handler := func(ctx context.Context, req *gemini.Request) *gemini.Response {
|
||||
return gemini.Success("text/gemini", bytes.NewBufferString("you've found my page"))
|
||||
}
|
||||
|
||||
server, err := gemini.NewServer(context.Background(), tlsConf, "tcp", "127.0.0.1:0", handler)
|
||||
if err != nil {
|
||||
t.Fatalf("NewServer(): %s", err.Error())
|
||||
}
|
||||
|
||||
go server.Serve()
|
||||
defer server.Close()
|
||||
|
||||
u, err := url.Parse(fmt.Sprintf("gemini://%s/test", server.Address()))
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
cli := gemini.NewClient(testClientTLS())
|
||||
response, err := cli.RoundTrip(&gemini.Request{URL: u})
|
||||
if err != nil {
|
||||
t.Fatalf("RoundTrip(): %s", err.Error())
|
||||
}
|
||||
|
||||
if response.Status != gemini.StatusSuccess {
|
||||
t.Errorf("response status: expected %d, got %d", gemini.StatusSuccess, response.Status)
|
||||
}
|
||||
if response.Meta != "text/gemini" {
|
||||
t.Errorf("response meta: expected \"text/gemini\", got %q", response.Meta)
|
||||
}
|
||||
|
||||
if response.Body == nil {
|
||||
t.Fatal("succcess response has nil body")
|
||||
}
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadAll: %s", err.Error())
|
||||
}
|
||||
if string(body) != "you've found my page" {
|
||||
t.Errorf("response body: expected \"you've found my page\", got %q", string(body))
|
||||
}
|
||||
}
|
||||
|
||||
func testClientTLS() *tls.Config {
|
||||
return &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package gemini_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
func TestFallthrough(t *testing.T) {
|
||||
h1 := func(ctx context.Context, req *gemini.Request) *gemini.Response {
|
||||
if req.Path == "/one" {
|
||||
return gemini.Success("text/gemini", bytes.NewBufferString("one"))
|
||||
}
|
||||
return gemini.NotFound("nope")
|
||||
}
|
||||
|
||||
h2 := func(ctx context.Context, req *gemini.Request) *gemini.Response {
|
||||
if req.Path == "/two" {
|
||||
return gemini.Success("text/gemini", bytes.NewBufferString("two"))
|
||||
}
|
||||
return gemini.NotFound("no way")
|
||||
}
|
||||
|
||||
fth := gemini.FallthroughHandler(h1, h2)
|
||||
|
||||
u, err := url.Parse("gemini://test.local/one")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
resp := fth(context.Background(), &gemini.Request{URL: u})
|
||||
|
||||
if resp.Status != gemini.StatusSuccess {
|
||||
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
|
||||
}
|
||||
|
||||
if resp.Meta != "text/gemini" {
|
||||
t.Errorf(`expected meta "text/gemini", got %q`, resp.Meta)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Read: %s", err.Error())
|
||||
}
|
||||
if string(body) != "one" {
|
||||
t.Errorf(`expected body "one", got %q`, string(body))
|
||||
}
|
||||
|
||||
u, err = url.Parse("gemini://test.local/two")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
resp = fth(context.Background(), &gemini.Request{URL: u})
|
||||
|
||||
if resp.Status != gemini.StatusSuccess {
|
||||
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
|
||||
}
|
||||
|
||||
if resp.Meta != "text/gemini" {
|
||||
t.Errorf(`expected meta "text/gemini", got %q`, resp.Meta)
|
||||
}
|
||||
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Errorf("Read: %s", err.Error())
|
||||
}
|
||||
if string(body) != "two" {
|
||||
t.Errorf(`expected body "two", got %q`, string(body))
|
||||
}
|
||||
|
||||
u, err = url.Parse("gemini://test.local/three")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
resp = fth(context.Background(), &gemini.Request{URL: u})
|
||||
|
||||
if resp.Status != gemini.StatusNotFound {
|
||||
t.Errorf("expected status %d, got %d", gemini.StatusNotFound, resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
pred := func(ctx context.Context, req *gemini.Request) bool {
|
||||
return strings.HasPrefix(req.Path, "/allow")
|
||||
}
|
||||
base := func(ctx context.Context, req *gemini.Request) *gemini.Response {
|
||||
return gemini.Success("text/gemini", bytes.NewBufferString("allowed!"))
|
||||
}
|
||||
handler := gemini.Filter(pred, base, nil)
|
||||
|
||||
u, err := url.Parse("gemini://test.local/allow/please")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
resp := handler(context.Background(), &gemini.Request{URL: u})
|
||||
if resp.Status != gemini.StatusSuccess {
|
||||
t.Errorf("expected status %d, got %d", gemini.StatusSuccess, resp.Status)
|
||||
}
|
||||
|
||||
u, err = url.Parse("gemini://test.local/disallow/please")
|
||||
if err != nil {
|
||||
t.Fatalf("url.Parse: %s", err.Error())
|
||||
}
|
||||
|
||||
resp = handler(context.Background(), &gemini.Request{URL: u})
|
||||
if resp.Status != gemini.StatusNotFound {
|
||||
t.Errorf("expected status %d, got %d", gemini.StatusNotFound, resp.Status)
|
||||
}
|
||||
}
|
|
@ -248,7 +248,7 @@ func TestResponseClose(t *testing.T) {
|
|||
|
||||
resp = &gemini.Response{
|
||||
Status: gemini.StatusInput,
|
||||
Meta: "give me more",
|
||||
Meta: "give me more",
|
||||
}
|
||||
|
||||
if err := resp.Close(); err != nil {
|
||||
|
@ -272,7 +272,7 @@ func TestResponseWriteTo(t *testing.T) {
|
|||
clone := func(resp *gemini.Response) *gemini.Response {
|
||||
other := &gemini.Response{
|
||||
Status: resp.Status,
|
||||
Meta: resp.Meta,
|
||||
Meta: resp.Meta,
|
||||
}
|
||||
|
||||
if resp.Body != nil {
|
||||
|
@ -291,27 +291,44 @@ func TestResponseWriteTo(t *testing.T) {
|
|||
other.Body = bytes.NewBuffer(buf2)
|
||||
}
|
||||
|
||||
return resp
|
||||
return other
|
||||
}
|
||||
|
||||
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())
|
||||
table := []struct {
|
||||
name string
|
||||
response *gemini.Response
|
||||
}{
|
||||
{
|
||||
name: "simple success",
|
||||
response: gemini.Success(
|
||||
"text/gemini",
|
||||
bytes.NewBufferString("the body goes here"),
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "no body",
|
||||
response: gemini.Input("need more pls"),
|
||||
},
|
||||
}
|
||||
|
||||
rdbuf := make([]byte, wtbuf.Len())
|
||||
if n, err := r2.Read(rdbuf); err != nil {
|
||||
t.Fatalf("response.Read() -> %d: %s", n, err.Error())
|
||||
}
|
||||
for _, test := range table {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r1 := test.response
|
||||
r2 := clone(test.response)
|
||||
|
||||
if wtbuf.String() != string(rdbuf) {
|
||||
t.Fatalf("Read produced %q but WriteTo produced %q", string(rdbuf), wtbuf.String())
|
||||
rdbuf, err := io.ReadAll(r1)
|
||||
if err != nil {
|
||||
t.Fatalf("response.Read(): %s", err.Error())
|
||||
}
|
||||
|
||||
wtbuf := &bytes.Buffer{}
|
||||
if _, err := 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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,10 +32,12 @@ func NewServer(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
addr := listener.Addr()
|
||||
|
||||
s := &Server{
|
||||
ctx: ctx,
|
||||
network: network,
|
||||
address: address,
|
||||
network: addr.Network(),
|
||||
address: addr.String(),
|
||||
wg: &sync.WaitGroup{},
|
||||
listener: tls.NewListener(listener, tlsConfig),
|
||||
handler: handler,
|
||||
|
@ -59,7 +61,7 @@ func (s *Server) Serve() error {
|
|||
s.ctx, s.cancel = context.WithCancel(s.ctx)
|
||||
|
||||
s.wg.Add(1)
|
||||
go s.propagateCancel()
|
||||
s.propagateCancel()
|
||||
|
||||
for {
|
||||
conn, err := s.listener.Accept()
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIC7jCCAdYCAQcwDQYJKoZIhvcNAQELBQAwPTESMBAGA1UEAwwJbG9jYWxob3N0
|
||||
MQswCQYDVQQGEwJVUzEaMBgGA1UEBwwRU2FuIEZyYW5jaXNjbywgQ0EwHhcNMjMw
|
||||
MTExMjAwMDU5WhcNMjUwNDE1MjAwMDU5WjA9MRIwEAYDVQQDDAlsb2NhbGhvc3Qx
|
||||
CzAJBgNVBAYTAlVTMRowGAYDVQQHDBFTYW4gRnJhbmNpc2NvLCBDQTCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALlaPa1AxDQnMo0qQxY5/Bf7MNf1x6tN
|
||||
xjkpMnQnPM+cHmmlkEhI1zwLk/LrLxwq7+OOxMTPrJglrAiDAp1uCZHjKcTMFnwO
|
||||
9M5vf8LjtYBjZd8+OSHyYV37gxw7h9/Wsxl+1Yw40QaJKM9auj2xOyaDj5Ou9+yp
|
||||
CfbGSpVUTnqReOVFg2QSNwEviOZu1SvAouPyO98WKoXjn7K5mxE545e4mgF1EMht
|
||||
jB5kH6kXqZSUszlGA1MkX3AlDsYJIcYnDwelNvw6XTPpkT2wNehxPyD0iP4rs+W4
|
||||
5hgV8wYokpgrM3xxe0c4mop5bzrp2Hyz3WxnF7KwtJgHW/6YxhG73skCAwEAATAN
|
||||
BgkqhkiG9w0BAQsFAAOCAQEAfI+UE/3d0Fb8BZ2gtv1kUh8yx75LUbpg1aOEsZdP
|
||||
Rji+GkL5xiFDsm7BwqTKziAjDtjL2qtGcJJ835shsGiUSK6qJuf9C944utUvCoFm
|
||||
b4aUZ8fTmN7PkwRS61nIcHaS1zkiFzUdvbquV3QWSnl9kC+yDLHT0Z535tcvCMVM
|
||||
bO7JMj1sxml4Y9B/hfY7zAZJt1giSNH1iDeX2pTpmPPI40UsRn98cC8HZ0d8wFrv
|
||||
yc3hKkz8E+WTgZUf7jFk/KX/T5uwu+Y85emwfbb82KIR3oqhkJIfOfpqop2duZXB
|
||||
hMuO1QWEBkZ/hpfrAsN/foz8v46P9qgW8gfOfzhyBcqLvA==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,18 @@
|
|||
-----BEGIN CERTIFICATE REQUEST-----
|
||||
MIIC0zCCAbsCAQAwPTESMBAGA1UEAwwJbG9jYWxob3N0MQswCQYDVQQGEwJVUzEa
|
||||
MBgGA1UEBwwRU2FuIEZyYW5jaXNjbywgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
|
||||
DwAwggEKAoIBAQC5Wj2tQMQ0JzKNKkMWOfwX+zDX9cerTcY5KTJ0JzzPnB5ppZBI
|
||||
SNc8C5Py6y8cKu/jjsTEz6yYJawIgwKdbgmR4ynEzBZ8DvTOb3/C47WAY2XfPjkh
|
||||
8mFd+4McO4ff1rMZftWMONEGiSjPWro9sTsmg4+TrvfsqQn2xkqVVE56kXjlRYNk
|
||||
EjcBL4jmbtUrwKLj8jvfFiqF45+yuZsROeOXuJoBdRDIbYweZB+pF6mUlLM5RgNT
|
||||
JF9wJQ7GCSHGJw8HpTb8Ol0z6ZE9sDXocT8g9Ij+K7PluOYYFfMGKJKYKzN8cXtH
|
||||
OJqKeW866dh8s91sZxeysLSYB1v+mMYRu97JAgMBAAGgUTBPBgkqhkiG9w0BCQ4x
|
||||
QjBAMDEGA1UdJQQqMCgGCCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsG
|
||||
AQUFBwMEMAsGA1UdDwQEAwIFIDANBgkqhkiG9w0BAQsFAAOCAQEAOKb0Mnnm7oLT
|
||||
0fz7+CQ4KYva/dmr75k38PPRXGs/7Ls6nhu59yNhudHJtRyjaAzffwfg1NWxKlUV
|
||||
gDf+4K6S+cjz6bWVdU4XwH37V01GWWgzmwDGEsoZZpNstuq87BhI62BKQFKqJrw2
|
||||
pqNYoM+p4K7OnOUNT60LshzThguMb4h53YcTXyv7wAf9LABc4v0daVErunDZ5Elh
|
||||
QwlUZT/pngTLJiXDjrWB3PGnniTbC0OYhKKmFbX/dIR/TlUH7Fcc4mE9f514mU0n
|
||||
zys/mc57gBTdI11oIw1fkQJ6f3LDk3MsFfJntwhxjVeSXJUNOBwsxmxdyigjsifY
|
||||
J+SpNczO1Q==
|
||||
-----END CERTIFICATE REQUEST-----
|
|
@ -0,0 +1,27 @@
|
|||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpQIBAAKCAQEAuVo9rUDENCcyjSpDFjn8F/sw1/XHq03GOSkydCc8z5weaaWQ
|
||||
SEjXPAuT8usvHCrv447ExM+smCWsCIMCnW4JkeMpxMwWfA70zm9/wuO1gGNl3z45
|
||||
IfJhXfuDHDuH39azGX7VjDjRBokoz1q6PbE7JoOPk6737KkJ9sZKlVROepF45UWD
|
||||
ZBI3AS+I5m7VK8Ci4/I73xYqheOfsrmbETnjl7iaAXUQyG2MHmQfqReplJSzOUYD
|
||||
UyRfcCUOxgkhxicPB6U2/DpdM+mRPbA16HE/IPSI/iuz5bjmGBXzBiiSmCszfHF7
|
||||
RziainlvOunYfLPdbGcXsrC0mAdb/pjGEbveyQIDAQABAoIBAQC36ylkLu4Bahup
|
||||
I5RqC6NwEFpJEKLOAmB8+7oKs5yNzTYIUra2Y0DfXgWyd1fJtXlP7aymNgPm/QqV
|
||||
b5o6qKNqVWRu2Kw+8YBNDypRMi45dWfyewWp/55J6XYRn6iVna8dz1MKzp3qxFLw
|
||||
XfCLor802jqvqmBsPteaPOxo/LzatKhXp/mcO/hsxeMr1iSUVHTrQEIU/aIkmAqT
|
||||
/eXp/zVZk7O9Tx8wwCijB3v7j3zTEkcKSwFlAp0w01XeqllmqA5P9rW3vVGXJVIM
|
||||
t6t9C8XcJWPIOURz3JWZJpUBSZsyNe2N/wbCgkQV81A0s+4praKzgDbjE+njb0C/
|
||||
1CClbHV5AoGBAO/mnOzHe7ZJyYfuiu6ZR2REBY61n2J6DkL1stkN5xd+Op25afHT
|
||||
jLBjU98hM/AMtP1aHWFQpdEe0uyqRjV6PbpNE8j/m9AVfjZxzwR4ITW2xqUhXOSz
|
||||
89o832RO54TTr19YGnIhdU8dDQmYOcKmCSuw6KwCfHwBzkFuDFZGk/4/AoGBAMXK
|
||||
gzNyX3tN9Ug5AUo/Az4jQRSoyLjfnce0a0TF4jxEacUBx2COq3zaV/VADEFBla1t
|
||||
5roOAUyJ3V6fXtZnoqwZPYh6iGP8p7Tj6vyXI4SDktV0uAV57qSdajqxTrA7yoXr
|
||||
zrbxv3U/3vXr3JTsP42U5zp1m5n1VfVqCXBkynD3AoGBAOvs7JjDWXuctzASPNmH
|
||||
LjmB18FQBk3vYQUi4l8pmAF3pyejx3gGJw70r+/4lD5YEMozjD8+88Njv+T1U5SW
|
||||
Agysbm+2SMJr0LK0W/W2Olq7xEFzPQrBmmgeg0b/fhoXoBlw6JkjJF3IYSD1bqBp
|
||||
bw1jrn4y979weynHkyRpxnM7AoGBALGSzRPlPR/gr7P1qdjUlb61u/omRn7kFC11
|
||||
J1EJL8HX0fXTUQK5U/C1vn4q0FXN4elgX+LuK/BhXeNTxbtMM9m6l2nuSIEsFgzr
|
||||
Cs9XicWwsqT9MzGHdN9JjFPBV9oU9BAj0uSgSbmkbDHxXYo+SBh+dNIhQF+KyW+Z
|
||||
kXvcoXulAoGAA2hnEA17nJ7Vj1DZ4CoRblgjZFAMB64slcSesaorp3WWehvaXO8u
|
||||
jbvWuvj58DgvTLiv8xPIn4Zsjd0a77ysifvUcmxSRa/k9UIle/lwjmXGjQ1GSMEI
|
||||
FB5ZTqjLZwS9Y5BDxlPcYF7vqE9fNpcxmcfHGmSF5YAHvFOfGH6B63M=
|
||||
-----END RSA PRIVATE KEY-----
|
Reference in New Issue