internal/parser/http1/request_test.go

package http1

import (
	"bufio"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestReadRequest(t *testing.T) {
	cases := []struct {
		name string
		raw  string
		want Request
	}{
		{
			name: "simple_get",
			raw:  "GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n",
			want: Request{
				Method:  "GET",
				Target:  "/index.html",
				Version: "HTTP/1.1",
				Headers: [][2]string{{"Host", "example.com"}},
			},
		},
		{
			name: "post_with_body",
			raw:  "POST /api HTTP/1.1\r\nContent-Length: 5\r\n\r\nhello",
			want: Request{
				Method:  "POST",
				Target:  "/api",
				Version: "HTTP/1.1",
				Headers: [][2]string{{"Content-Length", "5"}},
				Body:    []byte("hello"),
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			r := bufio.NewReader(strings.NewReader(tc.raw))
			got, err := ReadRequest(r)
			if err != nil {
				t.Fatalf("ReadRequest: %v", err)
			}
			if diff := cmp.Diff(&tc.want, got); diff != "" {
				t.Errorf("mismatch (-want +got):\n%s", diff)
			}
		})
	}
}

func TestParseRequestLineErrors(t *testing.T) {
	cases := []struct {
		name string
		line string
	}{
		{"empty", ""},
		{"only_method", "GET\r\n"},
		{"bad_version", "GET / HTTP/9.0\r\n"},
		{"bad_method", "G ET / HTTP/1.1\r\n"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if _, err := parseRequestLine(tc.line); err == nil {
				t.Fatalf("expected error for %q", tc.line)
			}
		})
	}
}

func TestIsToken(t *testing.T) {
	for _, ok := range []string{"GET", "POST", "X-Custom"} {
		if !isToken(ok) {
			t.Errorf("isToken(%q) = false, want true", ok)
		}
	}
	for _, bad := range []string{"", "has space", "with/slash", "semi;colon"} {
		if isToken(bad) {
			t.Errorf("isToken(%q) = true, want false", bad)
		}
	}
}