220 lines
5.6 KiB
Go
220 lines
5.6 KiB
Go
|
|
package probe
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
// mockHTTPClient records requests and returns configurable responses
|
||
|
|
type mockHTTPClient struct {
|
||
|
|
Resp *http.Response
|
||
|
|
Err error
|
||
|
|
}
|
||
|
|
|
||
|
|
func (m *mockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||
|
|
// Simulate context cancellation: if the request context is done, return context error
|
||
|
|
select {
|
||
|
|
case <-req.Context().Done():
|
||
|
|
return nil, req.Context().Err()
|
||
|
|
default:
|
||
|
|
}
|
||
|
|
return m.Resp, m.Err
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_Success(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
w.Write([]byte(`{"status":"ok"}`))
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil) // nil → uses real http.Client
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 1,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
AuthHeader: "Bearer test-key",
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if outcome.StatusCode != http.StatusOK {
|
||
|
|
t.Fatalf("expected 200, got: %d", outcome.StatusCode)
|
||
|
|
}
|
||
|
|
if outcome.LatencyMs < 0 {
|
||
|
|
t.Fatalf("expected latency >= 0, got: %d", outcome.LatencyMs)
|
||
|
|
}
|
||
|
|
if outcome.RequestID == "" {
|
||
|
|
t.Fatal("expected request_id to be set")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_ExplicitFailure(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusUnauthorized)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 2,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if outcome.StatusCode != http.StatusUnauthorized {
|
||
|
|
t.Fatalf("expected 401, got: %d", outcome.StatusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_Inconclusive_429(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusTooManyRequests)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 3,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if outcome.StatusCode != http.StatusTooManyRequests {
|
||
|
|
t.Fatalf("expected 429, got: %d", outcome.StatusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_TransportError(t *testing.T) {
|
||
|
|
client := &mockHTTPClient{
|
||
|
|
Err: errors.New("connection refused"),
|
||
|
|
}
|
||
|
|
executor := NewProbeExecutor(client)
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 4,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: "http://localhost:9999",
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if outcome.TransportError == nil {
|
||
|
|
t.Fatal("expected transport error to be set")
|
||
|
|
}
|
||
|
|
if outcome.StatusCode != 0 {
|
||
|
|
t.Fatalf("expected status 0 on transport error, got: %d", outcome.StatusCode)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_InvalidTarget(t *testing.T) {
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
_, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 5,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: "", // empty endpoint
|
||
|
|
})
|
||
|
|
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("expected error for empty endpoint")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_ContextCanceled(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
time.Sleep(5 * time.Second) // delay longer than context
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
|
||
|
|
defer cancel()
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(ctx, ProbeTarget{
|
||
|
|
AccountID: 6,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if outcome.TransportError == nil {
|
||
|
|
t.Fatal("expected context deadline exceeded transport error")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_ExecuteProbe_ResponseBodyTruncated(t *testing.T) {
|
||
|
|
largeBody := strings.Repeat("x", 10*1024) // 10KB
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
w.Write([]byte(largeBody))
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
outcome, err := executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 7,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
})
|
||
|
|
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("unexpected error: %v", err)
|
||
|
|
}
|
||
|
|
if len(outcome.ResponseBody) > 1024 {
|
||
|
|
t.Fatalf("expected body truncated to <=1024, got: %d", len(outcome.ResponseBody))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestProbeExecutor_SetsUserAgentAndAcceptHeader(t *testing.T) {
|
||
|
|
var receivedHeaders http.Header
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||
|
|
receivedHeaders = r.Header.Clone()
|
||
|
|
w.WriteHeader(http.StatusOK)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
|
||
|
|
executor := NewProbeExecutor(nil)
|
||
|
|
|
||
|
|
_, _ = executor.ExecuteProbe(context.Background(), ProbeTarget{
|
||
|
|
AccountID: 8,
|
||
|
|
Platform: "openai",
|
||
|
|
Endpoint: server.URL,
|
||
|
|
AuthHeader: "Bearer my-key",
|
||
|
|
})
|
||
|
|
|
||
|
|
if receivedHeaders == nil {
|
||
|
|
t.Fatal("server handler was not called — check test setup")
|
||
|
|
}
|
||
|
|
if receivedHeaders.Get("User-Agent") == "" {
|
||
|
|
t.Fatal("expected User-Agent header to be set")
|
||
|
|
}
|
||
|
|
if receivedHeaders.Get("Accept") != "application/json" {
|
||
|
|
t.Fatalf("expected Accept: application/json, got: %s", receivedHeaders.Get("Accept"))
|
||
|
|
}
|
||
|
|
if receivedHeaders.Get("Authorization") != "Bearer my-key" {
|
||
|
|
t.Fatalf("expected Authorization header to be set")
|
||
|
|
}
|
||
|
|
}
|