2024-05-25 18:27:53 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"log"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
)
|
|
|
|
|
2024-05-26 02:43:27 +02:00
|
|
|
var (
|
|
|
|
client = &http.Client{Transport: &http.Transport{DisableCompression: true}}
|
|
|
|
)
|
|
|
|
|
2024-05-25 18:27:53 +02:00
|
|
|
func getBody(body io.ReadCloser) []byte {
|
|
|
|
defer body.Close()
|
|
|
|
bodyBytes, err := io.ReadAll(body)
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("Failed to read res body: %v", err)
|
|
|
|
}
|
|
|
|
return bodyBytes
|
|
|
|
}
|
|
|
|
|
|
|
|
type Expected struct {
|
|
|
|
status int
|
|
|
|
body string
|
|
|
|
headers map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkResponse(t *testing.T, res *http.Response, expected Expected) {
|
|
|
|
if res.StatusCode != expected.status {
|
|
|
|
t.Errorf("Expected status code %d, received %d", expected.status, res.StatusCode)
|
|
|
|
}
|
|
|
|
|
|
|
|
body := string(getBody(res.Body))
|
|
|
|
if body != expected.body {
|
|
|
|
t.Errorf(`Expected body to be "%s" but got "%s"`, expected.body, body)
|
|
|
|
}
|
|
|
|
|
2024-05-26 02:04:59 +02:00
|
|
|
log.Println("HEADERS", res.Header)
|
2024-05-25 18:27:53 +02:00
|
|
|
for header, value := range expected.headers {
|
2024-05-26 02:04:59 +02:00
|
|
|
if res.Header[header] == nil {
|
|
|
|
t.Errorf(`Expected "%s" header to be present`, header)
|
|
|
|
} else if actual := res.Header[header][0]; actual != value {
|
2024-05-25 18:27:53 +02:00
|
|
|
t.Errorf(`Expected "%s" header to be "%s" but got "%s"`, header, value, actual)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestRoot(t *testing.T) {
|
|
|
|
res, _ := http.Get("http://localhost:4221")
|
|
|
|
checkResponse(t, res, Expected{status: 200})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestNotFound(t *testing.T) {
|
|
|
|
res, _ := http.Get("http://localhost:4221/foo")
|
|
|
|
checkResponse(t, res, Expected{status: 404})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestEcho(t *testing.T) {
|
|
|
|
input := "abc"
|
2024-05-26 02:43:27 +02:00
|
|
|
req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:4221/echo/%s", input), nil)
|
|
|
|
res, _ := client.Do(req)
|
2024-05-25 18:27:53 +02:00
|
|
|
checkResponse(t, res, Expected{status: 200, body: input, headers: map[string]string{
|
|
|
|
"Content-Length": strconv.Itoa(len(input)),
|
|
|
|
"Content-Type": "text/plain",
|
|
|
|
}})
|
|
|
|
}
|
|
|
|
|
2024-05-26 02:04:59 +02:00
|
|
|
func TestEchoGzip(t *testing.T) {
|
|
|
|
input := "abc"
|
|
|
|
req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:4221/echo/%s", input), nil)
|
|
|
|
req.Header.Set("Accept-Encoding", "gzip")
|
|
|
|
res, _ := client.Do(req)
|
2024-05-26 01:03:46 +02:00
|
|
|
|
2024-05-26 02:43:27 +02:00
|
|
|
asGzip := string(gzipCompress([]byte(input)))
|
|
|
|
checkResponse(t, res, Expected{status: 200, body: asGzip, headers: map[string]string{
|
|
|
|
"Content-Length": strconv.Itoa(len(asGzip)),
|
2024-05-26 02:04:59 +02:00
|
|
|
"Content-Type": "text/plain",
|
|
|
|
"Content-Encoding": "gzip",
|
|
|
|
}})
|
|
|
|
}
|
2024-05-26 01:03:46 +02:00
|
|
|
|
2024-05-25 18:27:53 +02:00
|
|
|
func TestUserAgent(t *testing.T) {
|
|
|
|
input := "CodeCrafters/1.0"
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost:4221/user-agent", nil)
|
|
|
|
req.Header.Set("User-Agent", input)
|
|
|
|
res, _ := client.Do(req)
|
|
|
|
checkResponse(t, res, Expected{status: 200, body: input, headers: map[string]string{
|
2024-05-26 02:43:27 +02:00
|
|
|
// "Content-Length": strconv.Itoa(len(input)),
|
|
|
|
"Content-Type": "text/plain",
|
2024-05-25 18:27:53 +02:00
|
|
|
}})
|
|
|
|
}
|
|
|
|
func TestUserAgentNoHeader(t *testing.T) {
|
|
|
|
req, _ := http.NewRequest("GET", "http://localhost:4221/user-agent", nil)
|
|
|
|
req.Header.Set("User-Agent", "")
|
|
|
|
res, _ := client.Do(req)
|
|
|
|
checkResponse(t, res, Expected{status: 400})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestReadFile(t *testing.T) {
|
|
|
|
input := "Hello World"
|
|
|
|
tmp, _ := os.CreateTemp("", "read.txt")
|
|
|
|
defer os.Remove(tmp.Name())
|
|
|
|
os.WriteFile(tmp.Name(), []byte(input), 0755)
|
|
|
|
DIR = path.Dir(tmp.Name())
|
|
|
|
|
2024-05-26 02:43:27 +02:00
|
|
|
req, _ := http.NewRequest("GET", fmt.Sprintf("http://localhost:4221/files/%s", path.Base(tmp.Name())), nil)
|
|
|
|
res, _ := client.Do(req)
|
2024-05-25 18:27:53 +02:00
|
|
|
checkResponse(t, res, Expected{status: 200, body: input, headers: map[string]string{
|
|
|
|
"Content-Type": "application/octet-stream",
|
|
|
|
"Content-Length": strconv.Itoa(len(input)),
|
|
|
|
}})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWriteFile(t *testing.T) {
|
|
|
|
input := "Hello World"
|
|
|
|
tmp, _ := os.CreateTemp("", "write.txt")
|
|
|
|
defer os.Remove(tmp.Name())
|
|
|
|
DIR = path.Dir(tmp.Name())
|
|
|
|
|
2024-05-26 02:43:27 +02:00
|
|
|
req, _ := http.NewRequest("POST", fmt.Sprintf("http://localhost:4221/files/%s", path.Base(tmp.Name())), strings.NewReader(input))
|
|
|
|
req.Header.Set("Content-Type", "application/octet-stream")
|
|
|
|
res, _ := client.Do(req)
|
2024-05-25 18:27:53 +02:00
|
|
|
checkResponse(t, res, Expected{status: 201})
|
|
|
|
|
|
|
|
contents, _ := os.ReadFile(tmp.Name())
|
|
|
|
if string(contents) != input {
|
|
|
|
t.Errorf("Content written to file does not match the input")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
fmt.Println("Starting server")
|
|
|
|
l, err := net.Listen("tcp", "0.0.0.0:4221")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("Failed to bind to port 4221")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
conn, err := l.Accept()
|
|
|
|
if err == nil {
|
|
|
|
go handleConnection(conn, routes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
code := m.Run()
|
|
|
|
|
|
|
|
fmt.Println("Stopping server")
|
|
|
|
l.Close()
|
|
|
|
|
|
|
|
os.Exit(code)
|
|
|
|
}
|