- 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
451 lines
9.1 KiB
Go
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")
|
|
}
|
|
}
|