feat(testing): add comprehensive test coverage and code quality improvements #76
@@ -1,15 +1,13 @@
|
||||
package header
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewRequestFromBytes(t *testing.T) {
|
||||
func TestNewRequest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
@@ -93,94 +91,6 @@ func TestNewRequestFromBytes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequestFromReader(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
expectErr bool
|
||||
errContains string
|
||||
expectEOF bool
|
||||
expectMethod string
|
||||
expectPath string
|
||||
expectVersion string
|
||||
expectHeaders map[string]string
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
data: []byte("POST /api HTTP/1.1\r\nContent-Type: application/json\r\n\r\n"),
|
||||
expectErr: false,
|
||||
expectMethod: "POST",
|
||||
expectPath: "/api",
|
||||
expectVersion: "HTTP/1.1",
|
||||
expectHeaders: map[string]string{
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "read error on start line",
|
||||
data: []byte{},
|
||||
expectErr: true,
|
||||
expectEOF: true,
|
||||
},
|
||||
{
|
||||
name: "invalid start line",
|
||||
data: []byte("INVALID\n\n"),
|
||||
expectErr: true,
|
||||
errContains: "invalid start line",
|
||||
},
|
||||
{
|
||||
name: "read error on headers",
|
||||
data: []byte("GET / HTTP/1.1\nHost: example.com"),
|
||||
expectErr: true,
|
||||
expectEOF: true,
|
||||
},
|
||||
{
|
||||
name: "multiple colons in header",
|
||||
data: []byte("GET / HTTP/1.1\r\nX-Custom: value:with:colons\r\n\r\n"),
|
||||
expectErr: false,
|
||||
expectMethod: "GET",
|
||||
expectPath: "/",
|
||||
expectVersion: "HTTP/1.1",
|
||||
expectHeaders: map[string]string{
|
||||
"X-Custom": "value:with:colons",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
br := bufio.NewReader(bytes.NewReader(tt.data))
|
||||
req, err := NewRequest(br)
|
||||
if tt.expectErr {
|
||||
assert.Error(t, err)
|
||||
if tt.expectEOF {
|
||||
assert.Equal(t, io.EOF, err)
|
||||
}
|
||||
if tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
assert.Nil(t, req)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, req)
|
||||
assert.Equal(t, tt.expectMethod, req.Method())
|
||||
assert.Equal(t, tt.expectPath, req.Path())
|
||||
assert.Equal(t, tt.expectVersion, req.Version())
|
||||
for k, v := range tt.expectHeaders {
|
||||
assert.Equal(t, v, req.Value(k))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRequestUnsupportedType(t *testing.T) {
|
||||
req, err := NewRequest(123)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unsupported type: int")
|
||||
assert.Nil(t, req)
|
||||
}
|
||||
|
||||
func TestRequestHeaderMethods(t *testing.T) {
|
||||
data := []byte("GET / HTTP/1.1\r\nHost: original\r\n\r\n")
|
||||
req, _ := NewRequest(data)
|
||||
@@ -315,30 +225,3 @@ func TestSetRemainingHeaders(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHeadersFromReaderEdgeCases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
expectHeaders map[string]string
|
||||
}{
|
||||
{
|
||||
name: "malformed header line",
|
||||
data: []byte("GET / HTTP/1.1\r\nMalformedLine\r\nK1: V1\r\n\r\n"),
|
||||
expectHeaders: map[string]string{
|
||||
"K1": "V1",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
br := bufio.NewReader(bytes.NewReader(tt.data))
|
||||
req, err := parseHeadersFromReader(br)
|
||||
assert.NoError(t, err)
|
||||
for k, v := range tt.expectHeaders {
|
||||
assert.Equal(t, v, req.Value(k))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package header
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
@@ -36,31 +35,6 @@ func setRemainingHeaders(remaining []byte, header interface {
|
||||
}
|
||||
}
|
||||
|
||||
func parseHeadersFromBytes(headerData []byte) (RequestHeader, error) {
|
||||
header := &requestHeader{
|
||||
headers: make(map[string]string, 16),
|
||||
}
|
||||
|
||||
lineEnd := bytes.Index(headerData, []byte("\r\n"))
|
||||
if lineEnd == -1 {
|
||||
return nil, fmt.Errorf("invalid request: no CRLF found in start line")
|
||||
}
|
||||
|
||||
startLine := headerData[:lineEnd]
|
||||
header.startLine = startLine
|
||||
var err error
|
||||
header.method, header.path, header.version, err = parseStartLine(startLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remaining := headerData[lineEnd+2:]
|
||||
|
||||
setRemainingHeaders(remaining, header)
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func parseStartLine(startLine []byte) (method, path, version string, err error) {
|
||||
firstSpace := bytes.IndexByte(startLine, ' ')
|
||||
if firstSpace == -1 {
|
||||
@@ -80,51 +54,6 @@ func parseStartLine(startLine []byte) (method, path, version string, err error)
|
||||
return method, path, version, nil
|
||||
}
|
||||
|
||||
func parseHeadersFromReader(br *bufio.Reader) (RequestHeader, error) {
|
||||
header := &requestHeader{
|
||||
headers: make(map[string]string, 16),
|
||||
}
|
||||
|
||||
startLineBytes, err := br.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
startLineBytes = bytes.TrimRight(startLineBytes, "\r\n")
|
||||
header.startLine = make([]byte, len(startLineBytes))
|
||||
copy(header.startLine, startLineBytes)
|
||||
|
||||
header.method, header.path, header.version, err = parseStartLine(header.startLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for {
|
||||
lineBytes, err := br.ReadSlice('\n')
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
lineBytes = bytes.TrimRight(lineBytes, "\r\n")
|
||||
|
||||
if len(lineBytes) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
colonIdx := bytes.IndexByte(lineBytes, ':')
|
||||
if colonIdx == -1 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := bytes.TrimSpace(lineBytes[:colonIdx])
|
||||
value := bytes.TrimSpace(lineBytes[colonIdx+1:])
|
||||
|
||||
header.headers[string(key)] = string(value)
|
||||
}
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func finalize(startLine []byte, headers map[string]string) []byte {
|
||||
size := len(startLine) + 2
|
||||
for key, val := range headers {
|
||||
|
||||
@@ -1,19 +1,33 @@
|
||||
package header
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func NewRequest(r interface{}) (RequestHeader, error) {
|
||||
switch v := r.(type) {
|
||||
case []byte:
|
||||
return parseHeadersFromBytes(v)
|
||||
case *bufio.Reader:
|
||||
return parseHeadersFromReader(v)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type: %T", r)
|
||||
func NewRequest(headerData []byte) (RequestHeader, error) {
|
||||
header := &requestHeader{
|
||||
headers: make(map[string]string, 16),
|
||||
}
|
||||
|
||||
lineEnd := bytes.Index(headerData, []byte("\r\n"))
|
||||
if lineEnd == -1 {
|
||||
return nil, fmt.Errorf("invalid request: no CRLF found in start line")
|
||||
}
|
||||
|
||||
startLine := headerData[:lineEnd]
|
||||
header.startLine = startLine
|
||||
var err error
|
||||
header.method, header.path, header.version, err = parseStartLine(startLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remaining := headerData[lineEnd+2:]
|
||||
|
||||
setRemainingHeaders(remaining, header)
|
||||
|
||||
return header, nil
|
||||
}
|
||||
|
||||
func (req *requestHeader) Value(key string) string {
|
||||
|
||||
Reference in New Issue
Block a user