Add HW 3
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
package hw3test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestContractResponseRejectsBadFraming(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, response string
|
||||
valid bool
|
||||
}{
|
||||
{"ok", "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 4\r\n\r\ndata", true},
|
||||
{"short", "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 5\r\n\r\ndata", false},
|
||||
{"long", "HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 3\r\n\r\ndata", false},
|
||||
{"no-close", "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n", false},
|
||||
{"no-length", "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", false},
|
||||
{"204", "HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n", true},
|
||||
{"204-length", "HTTP/1.1 204 No Content\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", false},
|
||||
{"204-body", "HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\nx", false},
|
||||
{"chunked", "HTTP/1.1 200 OK\r\nConnection: close\r\nTransfer-Encoding: chunked\r\n\r\n0\r\n\r\n", false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
_, _, err := readContractResponse(bufio.NewReader(strings.NewReader(tc.response)), false)
|
||||
if (err == nil) != tc.valid {
|
||||
t.Fatalf("valid=%v, error=%v", tc.valid, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type resetReader struct{}
|
||||
|
||||
func (resetReader) Read([]byte) (int, error) {
|
||||
return 0, &net.OpError{Op: "read", Net: "tcp", Err: errors.New("connection reset")}
|
||||
}
|
||||
|
||||
func TestContractResponseAfterEarlyRejection(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name, body string
|
||||
requestHasBody bool
|
||||
valid bool
|
||||
}{
|
||||
{"complete error then reset", "error", true, true},
|
||||
{"reset without request body", "error", false, false},
|
||||
{"truncated error", "err", true, false},
|
||||
{"extra response byte", "error!", true, false},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
wire := "HTTP/1.1 409 Conflict\r\nConnection: close\r\nContent-Length: 5\r\n\r\n" + tc.body
|
||||
reader := bufio.NewReader(io.MultiReader(strings.NewReader(wire), resetReader{}))
|
||||
_, _, err := readContractResponse(reader, tc.requestHasBody)
|
||||
if (err == nil) != tc.valid {
|
||||
t.Fatalf("valid=%v, error=%v", tc.valid, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSnapshotDetectsErrorSideEffects(t *testing.T) {
|
||||
for _, mutation := range []string{"overwrite", "create", "delete", "mkdir"} {
|
||||
t.Run(mutation, func(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
file := filepath.Join(root, "file")
|
||||
if err := os.WriteFile(file, []byte("before"), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
before, err := snapshotTree(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err := snapshotTree(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareSnapshots(before, after); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
switch mutation {
|
||||
case "overwrite":
|
||||
err = os.WriteFile(file, []byte("after!"), 0644)
|
||||
case "create":
|
||||
err = os.WriteFile(filepath.Join(root, "new"), nil, 0644)
|
||||
case "delete":
|
||||
err = os.Remove(file)
|
||||
case "mkdir":
|
||||
err = os.Mkdir(filepath.Join(root, "new"), 0755)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
after, err = snapshotTree(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := compareSnapshots(before, after); err == nil {
|
||||
t.Fatal("side effect was accepted")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingPathActionsAndRoot(t *testing.T) {
|
||||
env := &Env{RootDir: &EnvDir{Listing: map[string]EnvNode{"file": &EnvFile{}, "dir": &EnvDir{Listing: map[string]EnvNode{}}}}}
|
||||
opts := &RunOpts{ServerDomain: "localhost"}
|
||||
for _, tc := range []struct {
|
||||
method, path string
|
||||
status int
|
||||
}{
|
||||
{"POST", "missing/new", 404}, {"POST", "file/new", 404},
|
||||
{"POST", "dir", 409}, {"PUT", "missing", 404}, {"PUT", "dir", 409}, {"DELETE", "missing", 404},
|
||||
{"POST", "/", 409}, {"PUT", "/", 409}, {"DELETE", "/", 403},
|
||||
} {
|
||||
q := Query{Method: tc.method, Path: tc.path, HostHeader: "LOCALHOST"}
|
||||
action, ok := q.Action(env, opts).(HttpErrorAction)
|
||||
if !ok || action.Status != tc.status {
|
||||
t.Errorf("%s %s: got %#v", tc.method, tc.path, q.Action(env, opts))
|
||||
}
|
||||
}
|
||||
q := Query{Method: "GET", Path: "", HostHeader: "LOCALHOST"}
|
||||
if _, ok := q.Action(env, opts).(GetDirAction); !ok {
|
||||
t.Fatal("root GET is not a listing")
|
||||
}
|
||||
for _, root := range []string{"", "/"} {
|
||||
for _, remove := range []bool{false, true} {
|
||||
q := Query{Method: "DELETE", Path: root, HostHeader: "localhost", RemoveDirectory: remove}
|
||||
action, ok := q.Action(env, opts).(HttpErrorAction)
|
||||
if !ok || action.Status != 403 {
|
||||
t.Fatalf("root DELETE with RemoveDirectory=%v: got %#v", remove, action)
|
||||
}
|
||||
q.HostHeader = "wrong.example"
|
||||
action, ok = q.Action(env, opts).(HttpErrorAction)
|
||||
if !ok || action.Status != 400 {
|
||||
t.Fatalf("Host must be checked before root DELETE: got %#v", action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user