250 lines
5.6 KiB
Go
250 lines
5.6 KiB
Go
package devtodev
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/klauspost/compress/zstd"
|
|
)
|
|
|
|
func TestSend_NoCompression(t *testing.T) {
|
|
payload := samplePayload()
|
|
expected, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("marshal payload: %v", err)
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Fatalf("method: %s", r.Method)
|
|
}
|
|
if ct := r.Header.Get("Content-Type"); ct != "application/json" {
|
|
t.Fatalf("content-type: %s", ct)
|
|
}
|
|
if r.URL.Query().Get("appId") != "app-123" {
|
|
t.Fatalf("appId: %s", r.URL.Query().Get("appId"))
|
|
}
|
|
got, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("read body: %v", err)
|
|
}
|
|
if !bytes.Equal(got, expected) {
|
|
t.Fatalf("body mismatch")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("app-123")
|
|
client.Endpoint = server.URL
|
|
|
|
if err := client.Send(context.Background(), payload); err != nil {
|
|
t.Fatalf("send: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSend_GzipCompression(t *testing.T) {
|
|
payload := samplePayload()
|
|
expected, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("marshal payload: %v", err)
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if ct := r.Header.Get("Content-Type"); ct != "application/gzip" {
|
|
t.Fatalf("content-type: %s", ct)
|
|
}
|
|
zr, err := gzip.NewReader(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("gzip reader: %v", err)
|
|
}
|
|
got, err := io.ReadAll(zr)
|
|
if err != nil {
|
|
t.Fatalf("read body: %v", err)
|
|
}
|
|
zr.Close()
|
|
if !bytes.Equal(got, expected) {
|
|
t.Fatalf("body mismatch")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("app-123")
|
|
client.Endpoint = server.URL
|
|
client.Compression = CompressionGzip
|
|
|
|
if err := client.Send(context.Background(), payload); err != nil {
|
|
t.Fatalf("send: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSend_ZstdCompression(t *testing.T) {
|
|
payload := samplePayload()
|
|
expected, err := json.Marshal(payload)
|
|
if err != nil {
|
|
t.Fatalf("marshal payload: %v", err)
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if ct := r.Header.Get("Content-Type"); ct != "application/zstd" {
|
|
t.Fatalf("content-type: %s", ct)
|
|
}
|
|
decoder, err := zstd.NewReader(r.Body)
|
|
if err != nil {
|
|
t.Fatalf("zstd reader: %v", err)
|
|
}
|
|
got, err := io.ReadAll(decoder)
|
|
if err != nil {
|
|
t.Fatalf("read body: %v", err)
|
|
}
|
|
decoder.Close()
|
|
if !bytes.Equal(got, expected) {
|
|
t.Fatalf("body mismatch")
|
|
}
|
|
w.WriteHeader(http.StatusOK)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("app-123")
|
|
client.Endpoint = server.URL
|
|
client.Compression = CompressionZstd
|
|
|
|
if err := client.Send(context.Background(), payload); err != nil {
|
|
t.Fatalf("send: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestValidatePayload(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
payload Payload
|
|
ok bool
|
|
}{
|
|
{
|
|
name: "empty",
|
|
payload: Payload{},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "missing device id",
|
|
payload: Payload{
|
|
Reports: []Report{{}},
|
|
},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "missing packages",
|
|
payload: Payload{
|
|
Reports: []Report{{DeviceID: "d1"}},
|
|
},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "missing events",
|
|
payload: Payload{
|
|
Reports: []Report{{DeviceID: "d1", Packages: []DevtodevPackage{{}}}},
|
|
},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "missing code",
|
|
payload: Payload{
|
|
Reports: []Report{{DeviceID: "d1", Packages: []DevtodevPackage{{events: []Event{RawEvent{"timestamp": int64(1)}}}}}},
|
|
},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "missing timestamp",
|
|
payload: Payload{
|
|
Reports: []Report{{DeviceID: "d1", Packages: []DevtodevPackage{{events: []Event{RawEvent{"code": "ce"}}}}}},
|
|
},
|
|
ok: false,
|
|
},
|
|
{
|
|
name: "ok",
|
|
payload: Payload{
|
|
Reports: []Report{{DeviceID: "d1", Packages: []DevtodevPackage{{events: []Event{RawEvent{"code": "ce", "timestamp": int64(1)}}}}}},
|
|
},
|
|
ok: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidatePayload(tc.payload)
|
|
if tc.ok && err != nil {
|
|
t.Fatalf("expected ok, got %v", err)
|
|
}
|
|
if !tc.ok && err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSendWithResponse_ParsesJSONError(t *testing.T) {
|
|
payload := samplePayload()
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
_, _ = w.Write([]byte(`{"message":"bad request"}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := NewClient("app-123")
|
|
client.Endpoint = server.URL
|
|
client.Retry.MaxAttempts = 1
|
|
|
|
resp, err := client.SendWithResponse(context.Background(), payload)
|
|
if err == nil {
|
|
t.Fatalf("expected error")
|
|
}
|
|
if resp == nil || resp.StatusCode != http.StatusBadRequest {
|
|
t.Fatalf("unexpected response: %+v", resp)
|
|
}
|
|
var apiErr *APIError
|
|
if !errors.As(err, &apiErr) {
|
|
t.Fatalf("expected APIError, got %T", err)
|
|
}
|
|
if apiErr.Message != "bad request" {
|
|
t.Fatalf("unexpected message: %q", apiErr.Message)
|
|
}
|
|
}
|
|
|
|
func samplePayload() Payload {
|
|
return Payload{
|
|
Reports: []Report{
|
|
{
|
|
DeviceID: "device-123",
|
|
Packages: []DevtodevPackage{
|
|
{
|
|
language: "en",
|
|
country: "US",
|
|
events: []Event{
|
|
RawEvent{
|
|
"code": "ce",
|
|
"timestamp": time.Now().UnixMilli(),
|
|
"level": 5,
|
|
"name": "custom_event",
|
|
"parameters": map[string]interface{}{
|
|
"level": 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|