164 lines
4.2 KiB
Go
164 lines
4.2 KiB
Go
package client
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/monlet/agent/internal/scheduler"
|
|
)
|
|
|
|
func TestSendHeartbeatSuccess(t *testing.T) {
|
|
var got HeartbeatRequest
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/v1/heartbeat" || r.Header.Get("Authorization") != "Bearer tok" {
|
|
http.Error(w, "bad", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
_ = json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusAccepted)
|
|
_, _ = io.WriteString(w, `{"accepted":true}`)
|
|
}))
|
|
defer srv.Close()
|
|
c := New(srv.URL, "tok", "a1")
|
|
err := c.SendHeartbeat(context.Background(), HeartbeatRequest{
|
|
AgentID: "a1",
|
|
ObservedAt: time.Now().UTC(),
|
|
Hostname: "h",
|
|
Features: AgentFeatures{Push: true, Metrics: false},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.AgentID != "a1" {
|
|
t.Fatalf("got %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestSendEventsBatch(t *testing.T) {
|
|
var got EventBatchRequest
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_ = json.NewDecoder(r.Body).Decode(&got)
|
|
w.WriteHeader(http.StatusAccepted)
|
|
_, _ = io.WriteString(w, `{"accepted":2,"deduplicated":0}`)
|
|
}))
|
|
defer srv.Close()
|
|
c := New(srv.URL, "tok", "a1")
|
|
resp, err := c.SendEvents(context.Background(), []scheduler.Event{{EventID: "x"}, {EventID: "y"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if resp.Accepted != 2 || got.AgentID != "a1" || len(got.Events) != 2 {
|
|
t.Fatalf("resp=%+v got=%+v", resp, got)
|
|
}
|
|
}
|
|
|
|
func TestSendEventsSplitsByJSONSize(t *testing.T) {
|
|
var (
|
|
requests int
|
|
total int
|
|
)
|
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
t.Error(err)
|
|
http.Error(w, "read body", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
if len(body) > MaxBatchBytes {
|
|
t.Errorf("body size %d exceeds %d", len(body), MaxBatchBytes)
|
|
http.Error(w, "too large", http.StatusRequestEntityTooLarge)
|
|
return
|
|
}
|
|
var got EventBatchRequest
|
|
if err := json.Unmarshal(body, &got); err != nil {
|
|
t.Error(err)
|
|
http.Error(w, "bad json", http.StatusBadRequest)
|
|
return
|
|
}
|
|
requests++
|
|
total += len(got.Events)
|
|
w.WriteHeader(http.StatusAccepted)
|
|
_ = json.NewEncoder(w).Encode(EventBatchResponse{Accepted: len(got.Events)})
|
|
}))
|
|
defer srv.Close()
|
|
|
|
events := make([]scheduler.Event, MaxBatchEvents)
|
|
for i := range events {
|
|
events[i] = scheduler.Event{
|
|
EventID: "event-" + time.Unix(int64(i), 0).UTC().Format("20060102150405"),
|
|
CheckID: "check",
|
|
ObservedAt: time.Unix(int64(i), 0).UTC(),
|
|
Status: "ok",
|
|
Output: strings.Repeat("x", 8192),
|
|
}
|
|
}
|
|
c := New(srv.URL, "tok", "a1")
|
|
resp, err := c.SendEvents(context.Background(), events)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if requests < 2 {
|
|
t.Fatalf("expected split batch, got %d request", requests)
|
|
}
|
|
if total != MaxBatchEvents || resp.Accepted != MaxBatchEvents {
|
|
t.Fatalf("total=%d resp=%+v", total, resp)
|
|
}
|
|
}
|
|
|
|
func TestSendEventsTooBig(t *testing.T) {
|
|
c := New("http://x", "t", "a1")
|
|
big := make([]scheduler.Event, MaxBatchEvents+1)
|
|
_, err := c.SendEvents(context.Background(), big)
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if Retryable(err) {
|
|
t.Fatal("oversized batch should not retry")
|
|
}
|
|
}
|
|
|
|
func TestSendEventsSingleEventTooLargeIsNotRetryable(t *testing.T) {
|
|
c := New("http://x", "t", "a1")
|
|
_, err := c.SendEvents(context.Background(), []scheduler.Event{{
|
|
EventID: "x",
|
|
CheckID: strings.Repeat("c", MaxBatchBytes),
|
|
Status: "ok",
|
|
}})
|
|
if err == nil {
|
|
t.Fatal("expected error")
|
|
}
|
|
if Retryable(err) {
|
|
t.Fatal("oversized single event should not retry")
|
|
}
|
|
}
|
|
|
|
func TestRetryableClassification(t *testing.T) {
|
|
if Retryable(&HTTPError{Status: 400, Body: ""}) {
|
|
t.Fatal("4xx should not retry")
|
|
}
|
|
if !Retryable(&HTTPError{Status: 503}) {
|
|
t.Fatal("5xx should retry")
|
|
}
|
|
if !Retryable(context.DeadlineExceeded) {
|
|
t.Fatal("network error should retry")
|
|
}
|
|
}
|
|
|
|
func TestBackoffBounds(t *testing.T) {
|
|
for a := 1; a <= 10; a++ {
|
|
d := Delay(a)
|
|
if d < BackoffMin*4/5 {
|
|
t.Fatalf("attempt %d: delay %s below min", a, d)
|
|
}
|
|
if d > BackoffMax*6/5 {
|
|
t.Fatalf("attempt %d: delay %s above max", a, d)
|
|
}
|
|
}
|
|
}
|