fetch_ml/tests/unit/api/helpers/payload_parser_test.go
Jeremie Fraeys 7305e2bc21
test: add comprehensive test coverage and command improvements
- Add logs and debug end-to-end tests
- Add test helper utilities
- Improve test fixtures and templates
- Update API server and config lint commands
- Add multi-user database initialization
2026-02-16 20:38:15 -05:00

451 lines
9.1 KiB
Go

package helpers_test
import (
"bytes"
"testing"
"github.com/jfraeys/fetch_ml/internal/api/helpers"
)
func TestNewPayloadParser(t *testing.T) {
payload := []byte{0x01, 0x02, 0x03, 0x04, 0x05}
p := helpers.NewPayloadParser(payload, 2)
if p.Offset() != 2 {
t.Errorf("expected offset 2, got %d", p.Offset())
}
if !bytes.Equal(p.Payload(), payload) {
t.Error("payload mismatch")
}
}
func TestPayloadParser_ParseByte(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
want byte
wantErr bool
}{
{
name: "valid byte",
payload: []byte{0x00, 0x01, 0x02},
start: 1,
want: 0x01,
wantErr: false,
},
{
name: "end of payload",
payload: []byte{0x00, 0x01},
start: 2,
want: 0,
wantErr: true,
},
{
name: "empty payload",
payload: []byte{},
start: 0,
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseByte()
if (err != nil) != tt.wantErr {
t.Errorf("ParseByte() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseByte() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_ParseUint16(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
want uint16
wantErr bool
}{
{
name: "valid uint16",
payload: []byte{0x01, 0x02, 0x03, 0x04},
start: 0,
want: 0x0102,
wantErr: false,
},
{
name: "another valid uint16",
payload: []byte{0x00, 0xAB, 0xCD},
start: 1,
want: 0xABCD,
wantErr: false,
},
{
name: "too short",
payload: []byte{0x00, 0x01},
start: 1,
want: 0,
wantErr: true,
},
{
name: "empty at end",
payload: []byte{0x00, 0x01, 0x02},
start: 3,
want: 0,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseUint16()
if (err != nil) != tt.wantErr {
t.Errorf("ParseUint16() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseUint16() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_ParseLengthPrefixedString(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
want string
wantErr bool
}{
{
name: "valid string",
payload: []byte{0x05, 'h', 'e', 'l', 'l', 'o'},
start: 0,
want: "hello",
wantErr: false,
},
{
name: "empty string",
payload: []byte{0x00, 0x01, 0x02},
start: 0,
want: "",
wantErr: false,
},
{
name: "from offset",
payload: []byte{0x00, 0x03, 'f', 'o', 'o'},
start: 1,
want: "foo",
wantErr: false,
},
{
name: "too short for length",
payload: []byte{0x05, 'h', 'i'},
start: 0,
want: "",
wantErr: true,
},
{
name: "no length byte",
payload: []byte{},
start: 0,
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseLengthPrefixedString()
if (err != nil) != tt.wantErr {
t.Errorf("ParseLengthPrefixedString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseLengthPrefixedString() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_ParseUint16PrefixedString(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
want string
wantErr bool
}{
{
name: "valid string",
payload: []byte{0x00, 0x05, 'h', 'e', 'l', 'l', 'o'},
start: 0,
want: "hello",
wantErr: false,
},
{
name: "empty string",
payload: []byte{0x00, 0x00, 0x01, 0x02},
start: 0,
want: "",
wantErr: false,
},
{
name: "from offset",
payload: []byte{0x00, 0x00, 0x00, 0x03, 'b', 'a', 'r'},
start: 2,
want: "bar",
wantErr: false,
},
{
name: "too short for length",
payload: []byte{0x00},
start: 0,
want: "",
wantErr: true,
},
{
name: "too short for string",
payload: []byte{0x00, 0x05, 'h', 'i'},
start: 0,
want: "",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseUint16PrefixedString()
if (err != nil) != tt.wantErr {
t.Errorf("ParseUint16PrefixedString() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseUint16PrefixedString() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_ParseBool(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
want bool
wantErr bool
}{
{
name: "true",
payload: []byte{0x01, 0x00},
start: 0,
want: true,
wantErr: false,
},
{
name: "false",
payload: []byte{0x00, 0x01},
start: 0,
want: false,
wantErr: false,
},
{
name: "non-zero is true",
payload: []byte{0xFF},
start: 0,
want: true,
wantErr: false,
},
{
name: "empty",
payload: []byte{},
start: 0,
want: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseBool()
if (err != nil) != tt.wantErr {
t.Errorf("ParseBool() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParseBool() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_ParseFixedBytes(t *testing.T) {
tests := []struct {
name string
payload []byte
start int
length int
want []byte
wantErr bool
}{
{
name: "valid",
payload: []byte{0x00, 0x01, 0x02, 0x03, 0x04},
start: 1,
length: 3,
want: []byte{0x01, 0x02, 0x03},
wantErr: false,
},
{
name: "exact length",
payload: []byte{0x00, 0x01, 0x02},
start: 0,
length: 3,
want: []byte{0x00, 0x01, 0x02},
wantErr: false,
},
{
name: "too long",
payload: []byte{0x00, 0x01},
start: 0,
length: 3,
want: nil,
wantErr: true,
},
{
name: "from end",
payload: []byte{0x00, 0x01, 0x02},
start: 2,
length: 2,
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := helpers.NewPayloadParser(tt.payload, tt.start)
got, err := p.ParseFixedBytes(tt.length)
if (err != nil) != tt.wantErr {
t.Errorf("ParseFixedBytes() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !bytes.Equal(got, tt.want) {
t.Errorf("ParseFixedBytes() = %v, want %v", got, tt.want)
}
})
}
}
func TestPayloadParser_Offset(t *testing.T) {
payload := []byte{0x00, 0x01, 0x02, 0x03}
p := helpers.NewPayloadParser(payload, 1)
if p.Offset() != 1 {
t.Errorf("initial Offset() = %d, want 1", p.Offset())
}
p.ParseByte()
if p.Offset() != 2 {
t.Errorf("after ParseByte() Offset() = %d, want 2", p.Offset())
}
}
func TestPayloadParser_Remaining(t *testing.T) {
payload := []byte{0x00, 0x01, 0x02, 0x03}
p := helpers.NewPayloadParser(payload, 2)
remaining := p.Remaining()
if !bytes.Equal(remaining, []byte{0x02, 0x03}) {
t.Errorf("Remaining() = %v, want [0x02, 0x03]", remaining)
}
// At end
p = helpers.NewPayloadParser(payload, 4)
if p.Remaining() != nil {
t.Errorf("at end Remaining() = %v, want nil", p.Remaining())
}
// Beyond end
p = helpers.NewPayloadParser(payload, 5)
if p.Remaining() != nil {
t.Errorf("beyond end Remaining() = %v, want nil", p.Remaining())
}
}
func TestPayloadParser_HasRemaining(t *testing.T) {
payload := []byte{0x00, 0x01}
tests := []struct {
start int
want bool
}{
{0, true},
{1, true},
{2, false},
{3, false},
}
for _, tt := range tests {
p := helpers.NewPayloadParser(payload, tt.start)
if got := p.HasRemaining(); got != tt.want {
t.Errorf("HasRemaining() with start=%d = %v, want %v", tt.start, got, tt.want)
}
}
}
func TestPayloadParser_ChainedParsing(t *testing.T) {
// Simulate a real protocol: [api_key:2][name_len:1][name:var][value:2]
payload := []byte{
0xAB, 0xCD, // api_key
0x05, // name_len
'h', 'e', 'l', 'l', 'o', // name
0x00, 0x42, // value
}
p := helpers.NewPayloadParser(payload, 0)
// Parse api key (2 bytes)
apiKey, err := p.ParseFixedBytes(2)
if err != nil {
t.Fatalf("failed to parse api key: %v", err)
}
if !bytes.Equal(apiKey, []byte{0xAB, 0xCD}) {
t.Errorf("api key = %v, want [0xAB, 0xCD]", apiKey)
}
// Parse name
name, err := p.ParseLengthPrefixedString()
if err != nil {
t.Fatalf("failed to parse name: %v", err)
}
if name != "hello" {
t.Errorf("name = %q, want 'hello'", name)
}
// Parse value
value, err := p.ParseUint16()
if err != nil {
t.Fatalf("failed to parse value: %v", err)
}
if value != 0x0042 {
t.Errorf("value = 0x%X, want 0x0042", value)
}
// Should be at end
if p.HasRemaining() {
t.Error("expected no remaining bytes")
}
}