Add HW 3
This commit is contained in:
@@ -0,0 +1,269 @@
|
||||
package hw3test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestResponseEndChecksWireBytes(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
response string
|
||||
valid bool
|
||||
}{
|
||||
{"valid body", "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndata", true},
|
||||
{"extra body bytes", "HTTP/1.1 200 OK\r\nContent-Length: 4\r\n\r\ndataextra", false},
|
||||
{"valid empty body", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", true},
|
||||
{"body after zero length", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\nextra", false},
|
||||
{"valid no content", "HTTP/1.1 204 No Content\r\n\r\n", true},
|
||||
{"body after no content", "HTTP/1.1 204 No Content\r\n\r\nextra", false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
go func() {
|
||||
server, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
_, _ = io.WriteString(server, tc.response)
|
||||
}()
|
||||
client, err := net.Dial("tcp", listener.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
reader := bufio.NewReader(client)
|
||||
response, err := http.ReadResponse(reader, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer response.Body.Close()
|
||||
if _, err := io.Copy(io.Discard, response.Body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := checkResponseEnd(reader, false); (err == nil) != tc.valid {
|
||||
t.Fatalf("valid=%v, checkResponseEnd error: %v", tc.valid, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseContentTypeRequiresSubtype(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
value string
|
||||
valid bool
|
||||
}{
|
||||
{"application/octet-stream", true},
|
||||
{"text/plain; charset=utf-8", true},
|
||||
{"nonsense", false},
|
||||
{"text/", false},
|
||||
{"", false},
|
||||
} {
|
||||
_, err := parseContentType(tc.value)
|
||||
if (err == nil) != tc.valid {
|
||||
t.Errorf("content type %q: valid=%v, error=%v", tc.value, tc.valid, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunQueryAcceptsEarlyErrorResponse(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
go func() {
|
||||
server, err := listener.Accept()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
reader := bufio.NewReader(server)
|
||||
for {
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil || line == "\r\n" {
|
||||
break
|
||||
}
|
||||
}
|
||||
_, _ = io.WriteString(server, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 5\r\n\r\nerror")
|
||||
}()
|
||||
t.Setenv("SOLUTION_HOST", "127.0.0.1")
|
||||
RunQuery(NewTestContext(t), &Env{}, t.TempDir(), RunOpts{
|
||||
Port: listener.Addr().(*net.TCPAddr).Port,
|
||||
ServerDomain: "localhost",
|
||||
}, Query{
|
||||
Method: "PUT",
|
||||
Path: "item",
|
||||
HostHeader: "other.example",
|
||||
FileContent: &EnvFile{GenSeed: 42, Size: 8 * 1024 * 1024},
|
||||
})
|
||||
}
|
||||
|
||||
func TestRunQueryWaitsForLargeRequestBeforeCloseTimeout(t *testing.T) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer listener.Close()
|
||||
done := make(chan error, 1)
|
||||
go func() {
|
||||
server, err := listener.Accept()
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
defer server.Close()
|
||||
request, err := http.ReadRequest(bufio.NewReader(server))
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
if _, err := io.WriteString(server, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 5\r\n\r\nerror"); err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
time.Sleep(2500 * time.Millisecond)
|
||||
_, err = io.Copy(io.Discard, request.Body)
|
||||
done <- err
|
||||
}()
|
||||
t.Setenv("SOLUTION_HOST", "127.0.0.1")
|
||||
RunQuery(NewTestContext(t), &Env{}, t.TempDir(), RunOpts{
|
||||
Port: listener.Addr().(*net.TCPAddr).Port,
|
||||
ServerDomain: "localhost",
|
||||
}, Query{
|
||||
Method: "POST",
|
||||
Path: "item",
|
||||
HostHeader: "other.example",
|
||||
FileContent: &EnvFile{GenSeed: 42, Size: 160 << 20},
|
||||
})
|
||||
if err := <-done; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIdleDeadlineWriter(t *testing.T) {
|
||||
t.Run("stalled write", func(t *testing.T) {
|
||||
client, server := net.Pipe()
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
writer := &idleDeadlineWriter{Conn: client, Timeout: 50 * time.Millisecond}
|
||||
_, err := writer.Write([]byte("x"))
|
||||
var netErr net.Error
|
||||
if !errors.As(err, &netErr) || !netErr.Timeout() {
|
||||
t.Fatalf("expected write timeout, got %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("request body timeout", func(t *testing.T) {
|
||||
client, server := net.Pipe()
|
||||
defer client.Close()
|
||||
defer server.Close()
|
||||
release := make(chan struct{})
|
||||
defer close(release)
|
||||
go func() {
|
||||
_, _ = http.ReadRequest(bufio.NewReader(server))
|
||||
<-release
|
||||
}()
|
||||
writer := &idleDeadlineWriter{Conn: client, Timeout: 50 * time.Millisecond}
|
||||
request := (&Query{Method: "POST", FileContent: &EnvFile{GenSeed: 42, Size: 1 << 20}}).
|
||||
CreateRequest(NewTestContext(t), "http://localhost/item")
|
||||
if err := request.Write(writer); err == nil {
|
||||
t.Fatal("expected request write to time out")
|
||||
}
|
||||
var netErr net.Error
|
||||
if !errors.As(writer.lastError, &netErr) || !netErr.Timeout() {
|
||||
t.Fatalf("expected underlying write timeout, got %v", writer.lastError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompressedGetResponseValidation(t *testing.T) {
|
||||
file := &EnvFile{GenSeed: 42, Size: 1024}
|
||||
directory := &EnvDir{Listing: map[string]EnvNode{"item": file}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
body io.Reader
|
||||
validate func(*TC, *http.Response)
|
||||
}{
|
||||
{
|
||||
name: "file",
|
||||
body: file.Open(),
|
||||
validate: func(t *TC, resp *http.Response) {
|
||||
GetFileAction{File: file, Compression: true, VerifyHeaders: true}.VerifyResponse(t, nil, resp)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "directory",
|
||||
body: bytes.NewBufferString("item\n"),
|
||||
validate: func(t *TC, resp *http.Response) {
|
||||
GetDirAction{Dir: directory, Compression: true, VerifyHeaders: true}.VerifyResponse(t, nil, resp)
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(tt *testing.T) {
|
||||
testContext := NewTestContext(tt)
|
||||
var encoded bytes.Buffer
|
||||
writer := gzip.NewWriter(&encoded)
|
||||
_, err := io.Copy(writer, tc.body)
|
||||
if err != nil {
|
||||
tt.Fatal(err)
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
tt.Fatal(err)
|
||||
}
|
||||
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
ContentLength: int64(encoded.Len()),
|
||||
Header: http.Header{
|
||||
"Content-Length": {strconv.Itoa(encoded.Len())},
|
||||
"Content-Encoding": {"gzip"},
|
||||
"Content-Type": {"text/plain"},
|
||||
"Server": {"test-server"},
|
||||
"Connection": {"close"},
|
||||
},
|
||||
Body: io.NopCloser(bytes.NewReader(encoded.Bytes())),
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
q := Query{Gzip: true, VerifyHeaders: true}
|
||||
req := q.CreateRequest(testContext, "http://localhost/item")
|
||||
if got := req.Header.Get("Accept-Encoding"); got != "gzip" {
|
||||
tt.Fatalf("expected Accept-Encoding: gzip, got %q", got)
|
||||
}
|
||||
q.CommonValidate(testContext, req, resp)
|
||||
tc.validate(testContext, resp)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareFileContentBlocks(t *testing.T) {
|
||||
file := &EnvFile{GenSeed: 17, Size: 64*1024 + 17}
|
||||
context := NewTestContext(t)
|
||||
if err := CompareFileContent(context, file.Open(), file); err != nil {
|
||||
t.Fatalf("valid file was rejected: %v", err)
|
||||
}
|
||||
if err := CompareFileContent(context, io.LimitReader(file.Open(), file.Size-1), file); err == nil {
|
||||
t.Fatal("truncated file was accepted")
|
||||
}
|
||||
content, err := io.ReadAll(file.Open())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content[len(content)-1] ^= 1
|
||||
if err := CompareFileContent(context, bytes.NewReader(content), file); err == nil {
|
||||
t.Fatal("changed final byte was accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user