- Introduced new methods in the trace manager for retrieving events, trace info, nodes, logs, and spaces from storage, improving data access and management. - Updated the driver implementations to support loading and unarchiving traces, ensuring robust handling of trace data. - Enhanced error handling and logging in the OpenAI provider to facilitate better debugging and traceability of requests and responses. - Added a new TraceSpaceData struct to encapsulate space metadata along with key-value data for improved API responses.
212 lines
6.9 KiB
Go
212 lines
6.9 KiB
Go
package trace_test
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// TestGetEvents tests the events API endpoint
|
|
func TestGetEvents(t *testing.T) {
|
|
data := prepareTestTrace(t)
|
|
defer cleanupTestTrace(t, data)
|
|
|
|
// Test GET /traces/:traceID/events
|
|
requestURL := fmt.Sprintf("%s%s/trace/traces/%s/events", data.ServerURL, data.BaseURL, data.TraceID)
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
assert.NoError(t, err)
|
|
req.Header.Set("Authorization", "Bearer "+data.TokenInfo.AccessToken)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
assert.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected status code 200")
|
|
|
|
// Parse response
|
|
body, err := io.ReadAll(resp.Body)
|
|
assert.NoError(t, err)
|
|
|
|
var responseData map[string]interface{}
|
|
err = json.Unmarshal(body, &responseData)
|
|
assert.NoError(t, err)
|
|
|
|
// Verify response structure
|
|
assert.Equal(t, data.TraceID, responseData["id"], "Trace ID should match")
|
|
assert.NotNil(t, responseData["events"], "Should have events field")
|
|
|
|
events, ok := responseData["events"].([]interface{})
|
|
assert.True(t, ok, "Events should be an array")
|
|
assert.NotEmpty(t, events, "Events array should not be empty")
|
|
|
|
t.Logf("Retrieved %d events for trace %s", len(events), data.TraceID)
|
|
|
|
// Verify event types
|
|
eventTypes := make(map[string]bool)
|
|
for _, e := range events {
|
|
event, ok := e.(map[string]interface{})
|
|
if ok {
|
|
eventType, _ := event["Type"].(string)
|
|
eventTypes[eventType] = true
|
|
}
|
|
}
|
|
|
|
assert.True(t, eventTypes["init"], "Should have init event")
|
|
assert.True(t, eventTypes["node_start"], "Should have node_start events")
|
|
assert.True(t, eventTypes["node_complete"], "Should have node_complete events")
|
|
assert.True(t, eventTypes["space_created"], "Should have space_created event")
|
|
}
|
|
|
|
// TestGetEventsNotFound tests getting events for non-existent trace
|
|
func TestGetEventsNotFound(t *testing.T) {
|
|
data := prepareTestTrace(t)
|
|
defer cleanupTestTrace(t, data)
|
|
|
|
// Try to get events for non-existent trace
|
|
requestURL := fmt.Sprintf("%s%s/trace/traces/nonexistent/events", data.ServerURL, data.BaseURL)
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
assert.NoError(t, err)
|
|
req.Header.Set("Authorization", "Bearer "+data.TokenInfo.AccessToken)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
assert.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode, "Expected status code 404 for non-existent trace")
|
|
}
|
|
|
|
// TestGetEventsUnauthorized tests getting events without authentication
|
|
func TestGetEventsUnauthorized(t *testing.T) {
|
|
data := prepareTestTrace(t)
|
|
defer cleanupTestTrace(t, data)
|
|
|
|
// Try to get events without token
|
|
requestURL := fmt.Sprintf("%s%s/trace/traces/%s/events", data.ServerURL, data.BaseURL, data.TraceID)
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
assert.NoError(t, err)
|
|
|
|
client := &http.Client{Timeout: 10 * time.Second}
|
|
resp, err := client.Do(req)
|
|
assert.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusUnauthorized, resp.StatusCode, "Expected status code 401 without authentication")
|
|
}
|
|
|
|
// TestGetEventsSSE tests the events API endpoint in SSE streaming mode
|
|
func TestGetEventsSSE(t *testing.T) {
|
|
data := prepareTestTrace(t)
|
|
defer cleanupTestTrace(t, data)
|
|
|
|
// Test GET /traces/:traceID/events?stream=true
|
|
requestURL := fmt.Sprintf("%s%s/trace/traces/%s/events?stream=true", data.ServerURL, data.BaseURL, data.TraceID)
|
|
|
|
req, err := http.NewRequest("GET", requestURL, nil)
|
|
assert.NoError(t, err)
|
|
req.Header.Set("Authorization", "Bearer "+data.TokenInfo.AccessToken)
|
|
req.Header.Set("Accept", "text/event-stream")
|
|
|
|
client := &http.Client{Timeout: 30 * time.Second}
|
|
resp, err := client.Do(req)
|
|
assert.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
// Verify SSE response headers
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode, "Expected status code 200")
|
|
assert.Equal(t, "text/event-stream", resp.Header.Get("Content-Type"), "Expected text/event-stream content type")
|
|
assert.Equal(t, "no-cache", resp.Header.Get("Cache-Control"), "Expected no-cache")
|
|
assert.Equal(t, "keep-alive", resp.Header.Get("Connection"), "Expected keep-alive connection")
|
|
|
|
// Read SSE events
|
|
scanner := bufio.NewScanner(resp.Body)
|
|
events := make([]map[string]interface{}, 0)
|
|
var currentEvent map[string]interface{}
|
|
eventCount := 0
|
|
maxEvents := 50 // Limit to prevent infinite loop
|
|
|
|
for scanner.Scan() && eventCount < maxEvents {
|
|
line := scanner.Text()
|
|
|
|
// SSE format: "data: {...}"
|
|
if strings.HasPrefix(line, "data: ") {
|
|
dataStr := strings.TrimPrefix(line, "data: ")
|
|
|
|
// Check for [DONE] marker
|
|
if dataStr == "[DONE]" {
|
|
t.Log("Received [DONE] marker, stream completed")
|
|
break
|
|
}
|
|
|
|
// Parse JSON event data
|
|
var eventData map[string]interface{}
|
|
if err := json.Unmarshal([]byte(dataStr), &eventData); err != nil {
|
|
t.Logf("Failed to parse event data: %s, error: %v", dataStr, err)
|
|
continue
|
|
}
|
|
|
|
currentEvent = eventData
|
|
} else if line == "" && currentEvent != nil {
|
|
// Empty line marks end of an event
|
|
events = append(events, currentEvent)
|
|
eventCount++
|
|
currentEvent = nil
|
|
}
|
|
}
|
|
|
|
assert.NoError(t, scanner.Err(), "Should not have scanner errors")
|
|
assert.NotEmpty(t, events, "Should receive at least one SSE event")
|
|
|
|
t.Logf("Received %d SSE events for trace %s", len(events), data.TraceID)
|
|
|
|
// Verify event structure and types
|
|
eventTypes := make(map[string]int)
|
|
for i, event := range events {
|
|
// Verify required fields
|
|
assert.NotNil(t, event["Type"], "Event %d should have Type field", i)
|
|
assert.NotNil(t, event["TraceID"], "Event %d should have TraceID field", i)
|
|
assert.NotNil(t, event["Timestamp"], "Event %d should have Timestamp field", i)
|
|
|
|
// Verify TraceID matches
|
|
if traceID, ok := event["TraceID"].(string); ok {
|
|
assert.Equal(t, data.TraceID, traceID, "Event %d TraceID should match", i)
|
|
}
|
|
|
|
// Count event types
|
|
if eventType, ok := event["Type"].(string); ok {
|
|
eventTypes[eventType]++
|
|
}
|
|
}
|
|
|
|
// Verify expected event types
|
|
assert.Greater(t, eventTypes["init"], 0, "Should have at least one init event")
|
|
assert.Greater(t, eventTypes["node_start"], 0, "Should have at least one node_start event")
|
|
assert.Greater(t, eventTypes["node_complete"], 0, "Should have at least one node_complete event")
|
|
assert.Greater(t, eventTypes["complete"], 0, "Should have at least one complete event")
|
|
|
|
// Log event type distribution
|
|
t.Logf("Event type distribution: %+v", eventTypes)
|
|
|
|
// Verify event order: init should be first
|
|
if len(events) > 0 {
|
|
firstEventType, _ := events[0]["Type"].(string)
|
|
assert.Equal(t, "init", firstEventType, "First event should be init")
|
|
}
|
|
|
|
// Verify complete event is last (before [DONE])
|
|
if len(events) > 1 {
|
|
lastEventType, _ := events[len(events)-1]["Type"].(string)
|
|
assert.Equal(t, "complete", lastEventType, "Last event should be complete")
|
|
}
|
|
}
|