mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Enhance interaction recording and analysis features
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"io"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
@@ -64,6 +65,7 @@ func TestLoggingProxy_LogRequest(t *testing.T) {
|
||||
defer func() { _ = os.Unsetenv("LOG_PROXY_BODY") }()
|
||||
|
||||
lp := NewLoggingProxy("http://example.com", true)
|
||||
lp.LogBody = true
|
||||
|
||||
body := "test body content"
|
||||
req := httptest.NewRequest("POST", "http://example.com/api", strings.NewReader(body))
|
||||
@@ -77,4 +79,47 @@ func TestLoggingProxy_LogRequest(t *testing.T) {
|
||||
if string(readBody) != body {
|
||||
t.Errorf("Request body was consumed or changed, got %q, want %q", string(readBody), body)
|
||||
}
|
||||
|
||||
// Test truncation
|
||||
lp.MaxBodySize = 4
|
||||
req2 := httptest.NewRequest("POST", "http://example.com/api", strings.NewReader("1234567890"))
|
||||
req2.Header.Set("Content-Type", "text/plain")
|
||||
lp.LogRequest(req2)
|
||||
}
|
||||
|
||||
func TestLoggingProxy_LogResponse(t *testing.T) {
|
||||
lp := NewLoggingProxy("http://example.com", true)
|
||||
lp.LogBody = true
|
||||
|
||||
body := "response content"
|
||||
req := httptest.NewRequest("GET", "http://example.com/api", nil)
|
||||
w := httptest.NewRecorder()
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
_, _ = w.WriteString(body)
|
||||
res := w.Result()
|
||||
res.Request = req
|
||||
|
||||
lp.LogResponse(res)
|
||||
|
||||
// Check if body is still readable
|
||||
readBody, _ := io.ReadAll(res.Body)
|
||||
if string(readBody) != body {
|
||||
t.Errorf("Response body was consumed or changed, got %q, want %q", string(readBody), body)
|
||||
}
|
||||
|
||||
// Test with recorder
|
||||
tmpDir, _ := os.MkdirTemp("", "proxy-recorder-test")
|
||||
defer os.RemoveAll(tmpDir)
|
||||
recorder := NewRecorder(tmpDir)
|
||||
lp.SetRecorder(recorder)
|
||||
lp.RecordEnabled = true
|
||||
|
||||
lp.LogResponse(res)
|
||||
|
||||
// Verify recording exists
|
||||
interactionsDir := filepath.Join(tmpDir, "interactions", recorder.SessionID, "upstream", "api")
|
||||
files, _ := os.ReadDir(interactionsDir)
|
||||
if len(files) == 0 {
|
||||
t.Error("LogResponse did not record the interaction")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,26 @@ type Recorder struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// InteractionStats represents statistics for recorded interactions.
|
||||
type InteractionStats struct {
|
||||
TotalRequests int `json:"total_requests"`
|
||||
ByService map[string]int `json:"by_service"`
|
||||
BySession map[string]int `json:"by_session"`
|
||||
}
|
||||
|
||||
// Interaction represents a single recorded HTTP interaction.
|
||||
type Interaction struct {
|
||||
ID string `json:"id"`
|
||||
Session string `json:"session"`
|
||||
Category string `json:"category"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
File string `json:"file"`
|
||||
Counter int `json:"counter"`
|
||||
Status int `json:"status"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
|
||||
// NewRecorder creates a new HTTP interaction recorder.
|
||||
func NewRecorder(baseDir string) *Recorder {
|
||||
sessionID := time.Now().Format("20060102-150405") + "-" + fmt.Sprintf("%d", os.Getpid())
|
||||
@@ -221,3 +241,194 @@ func (r *Recorder) updateEnvFile(newVars map[string]string) error {
|
||||
|
||||
return os.WriteFile(envFile, data, 0644)
|
||||
}
|
||||
|
||||
// GetInteractionStats returns statistics about recorded interactions.
|
||||
func (r *Recorder) GetInteractionStats() (*InteractionStats, error) {
|
||||
stats := &InteractionStats{
|
||||
ByService: make(map[string]int),
|
||||
BySession: make(map[string]int),
|
||||
}
|
||||
|
||||
interactionsDir := filepath.Join(r.BaseDir, "interactions")
|
||||
if _, err := os.Stat(interactionsDir); os.IsNotExist(err) {
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(interactionsDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !info.IsDir() && strings.HasSuffix(info.Name(), ".http") {
|
||||
stats.TotalRequests++
|
||||
|
||||
// Extract category (self/upstream) and session from path
|
||||
// Path is like: .../interactions/<session>/<category>/...
|
||||
rel, err := filepath.Rel(interactionsDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := strings.Split(rel, string(filepath.Separator))
|
||||
if len(parts) >= 2 {
|
||||
sessionID := parts[0]
|
||||
category := parts[1]
|
||||
stats.BySession[sessionID]++
|
||||
stats.ByService[category]++
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return stats, err
|
||||
}
|
||||
|
||||
// ListInteractions returns a list of recorded interactions.
|
||||
func (r *Recorder) ListInteractions(sessionFilter, categoryFilter, sinceFilter string) ([]Interaction, error) {
|
||||
interactions := make([]Interaction, 0)
|
||||
interactionsDir := filepath.Join(r.BaseDir, "interactions")
|
||||
|
||||
if _, err := os.Stat(interactionsDir); os.IsNotExist(err) {
|
||||
return interactions, nil
|
||||
}
|
||||
|
||||
err := filepath.Walk(interactionsDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() || !strings.HasSuffix(info.Name(), ".http") {
|
||||
return nil
|
||||
}
|
||||
|
||||
rel, err := filepath.Rel(interactionsDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
parts := strings.Split(rel, string(filepath.Separator))
|
||||
if len(parts) < 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sessionID, category := parts[0], parts[1]
|
||||
if (sessionFilter != "" && sessionID != sessionFilter) || (categoryFilter != "" && category != categoryFilter) {
|
||||
return nil
|
||||
}
|
||||
|
||||
interaction, ok := r.parseInteractionFile(rel, path, parts)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if sinceFilter != "" && interaction.Timestamp != "" {
|
||||
fullTS := r.getFullTimestamp(sessionID, interaction.ID)
|
||||
|
||||
normalizedSince := strings.ReplaceAll(strings.ReplaceAll(sinceFilter, ":", "-"), " ", "-")
|
||||
if fullTS != "" && fullTS < normalizedSince {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
interactions = append(interactions, interaction)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return interactions, err
|
||||
}
|
||||
|
||||
func (r *Recorder) parseInteractionFile(rel, path string, parts []string) (Interaction, bool) {
|
||||
sessionID, category := parts[0], parts[1]
|
||||
filename := parts[len(parts)-1]
|
||||
fnParts := strings.Split(strings.TrimSuffix(filename, ".http"), "-")
|
||||
|
||||
date := ""
|
||||
if len(sessionID) >= 8 {
|
||||
date = sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
}
|
||||
|
||||
timestamp := ""
|
||||
|
||||
if len(fnParts) >= 4 {
|
||||
timeStr := fnParts[1] + ":" + fnParts[2] + ":" + fnParts[3]
|
||||
timestamp = timeStr
|
||||
|
||||
if date != "" {
|
||||
timestamp = date + " " + timeStr
|
||||
}
|
||||
}
|
||||
|
||||
requestPath := "/" + strings.Join(parts[2:len(parts)-1], "/")
|
||||
if requestPath == "/root" {
|
||||
requestPath = "/"
|
||||
}
|
||||
|
||||
method, counter := "UNKNOWN", 0
|
||||
if len(fnParts) >= 1 {
|
||||
_, _ = fmt.Sscanf(fnParts[0], "%d", &counter)
|
||||
}
|
||||
|
||||
if len(fnParts) >= 5 {
|
||||
method = fnParts[4]
|
||||
}
|
||||
|
||||
return Interaction{
|
||||
ID: filename,
|
||||
Session: sessionID,
|
||||
Category: category,
|
||||
Method: method,
|
||||
Path: requestPath,
|
||||
File: rel,
|
||||
Counter: counter,
|
||||
Status: r.peekStatus(path),
|
||||
Timestamp: timestamp,
|
||||
}, true
|
||||
}
|
||||
|
||||
func (r *Recorder) getFullTimestamp(sessionID, filename string) string {
|
||||
if len(sessionID) < 8 {
|
||||
return ""
|
||||
}
|
||||
|
||||
date := sessionID[0:4] + "-" + sessionID[4:6] + "-" + sessionID[6:8]
|
||||
fnParts := strings.Split(strings.TrimSuffix(filename, ".http"), "-")
|
||||
|
||||
if len(fnParts) < 4 {
|
||||
return ""
|
||||
}
|
||||
|
||||
return date + "-" + fnParts[1] + "-" + fnParts[2] + "-" + fnParts[3]
|
||||
}
|
||||
|
||||
func (r *Recorder) peekStatus(path string) int {
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines {
|
||||
if !strings.Contains(line, "// Response:") {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmedLine := strings.TrimPrefix(strings.TrimSpace(line), "//")
|
||||
trimmedLine = strings.TrimPrefix(strings.TrimSpace(trimmedLine), "Response:")
|
||||
trimmedLine = strings.TrimSpace(trimmedLine)
|
||||
|
||||
status := 0
|
||||
_, _ = fmt.Sscanf(trimmedLine, "%d", &status)
|
||||
|
||||
return status
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// GetInteractionContent returns the raw content of a recorded interaction.
|
||||
func (r *Recorder) GetInteractionContent(relPath string) ([]byte, error) {
|
||||
fullPath := filepath.Join(r.BaseDir, "interactions", relPath)
|
||||
return os.ReadFile(fullPath)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -339,3 +341,362 @@ func TestRecorder_EnvFile(t *testing.T) {
|
||||
t.Errorf("Expected ip to be 192.168.178.35, got %s", content["session"]["ip"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorder_GetInteractionStats(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-stats-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
r.SessionID = "20260215-120000-12345"
|
||||
|
||||
// Create some dummy interactions
|
||||
files := []string{
|
||||
"interactions/20260215-120000-12345/self/setup/0001-12-00-01.000-GET.http",
|
||||
"interactions/20260215-120000-12345/upstream/marge/0002-12-00-02.000-POST.http",
|
||||
"interactions/20260215-130000-67890/self/setup/0001-13-00-01.000-GET.http",
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
path := filepath.Join(tmpDir, f)
|
||||
os.MkdirAll(filepath.Dir(path), 0755)
|
||||
os.WriteFile(path, []byte("test"), 0644)
|
||||
}
|
||||
|
||||
stats, err := r.GetInteractionStats()
|
||||
if err != nil {
|
||||
t.Fatalf("GetInteractionStats failed: %v", err)
|
||||
}
|
||||
|
||||
if stats.TotalRequests != 3 {
|
||||
t.Errorf("Expected 3 total requests, got %d", stats.TotalRequests)
|
||||
}
|
||||
|
||||
if stats.ByService["self"] != 2 {
|
||||
t.Errorf("Expected 2 self requests, got %d", stats.ByService["self"])
|
||||
}
|
||||
|
||||
if stats.ByService["upstream"] != 1 {
|
||||
t.Errorf("Expected 1 upstream request, got %d", stats.ByService["upstream"])
|
||||
}
|
||||
|
||||
if stats.BySession["20260215-120000-12345"] != 2 {
|
||||
t.Errorf("Expected 2 requests for session 1, got %d", stats.BySession["20260215-120000-12345"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorder_ListInteractions(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-list-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
session1 := "20260215-120000-12345"
|
||||
session2 := "20260215-130000-67890"
|
||||
|
||||
// Create some dummy interactions
|
||||
files := []struct {
|
||||
path string
|
||||
content string
|
||||
}{
|
||||
{
|
||||
path: filepath.Join("interactions", session1, "self", "setup", "0001-12-00-01.555-GET.http"),
|
||||
content: "### GET /setup\n\n> {% \n // Response: 200 OK\n%}\n",
|
||||
},
|
||||
{
|
||||
path: filepath.Join("interactions", session1, "upstream", "marge", "0002-12-00-02.000-POST.http"),
|
||||
content: "### POST /marge\n\n> {% \n // Response: 201 Created\n%}\n",
|
||||
},
|
||||
{
|
||||
path: filepath.Join("interactions", session2, "self", "info", "0001-13-00-05.000-GET.http"),
|
||||
content: "### GET /info\n\n> {% \n // Response: 404 Not Found\n%}\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
path := filepath.Join(tmpDir, f.path)
|
||||
os.MkdirAll(filepath.Dir(path), 0755)
|
||||
os.WriteFile(path, []byte(f.content), 0644)
|
||||
}
|
||||
|
||||
t.Run("List_all", func(t *testing.T) {
|
||||
list, err := r.ListInteractions("", "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
if len(list) != 3 {
|
||||
t.Errorf("Expected 3 interactions, got %d", len(list))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_by_session", func(t *testing.T) {
|
||||
list, err := r.ListInteractions(session1, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
if len(list) != 2 {
|
||||
t.Errorf("Expected 2 interactions for session1, got %d", len(list))
|
||||
}
|
||||
for _, i := range list {
|
||||
if i.Session != session1 {
|
||||
t.Errorf("Expected session %s, got %s", session1, i.Session)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_by_category", func(t *testing.T) {
|
||||
list, err := r.ListInteractions("", "upstream", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Errorf("Expected 1 upstream interaction, got %d", len(list))
|
||||
}
|
||||
if list[0].Category != "upstream" {
|
||||
t.Errorf("Expected category upstream, got %s", list[0].Category)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Check_enhanced_fields", func(t *testing.T) {
|
||||
list, err := r.ListInteractions(session1, "self", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
if len(list) == 0 {
|
||||
t.Fatal("Expected at least one interaction")
|
||||
}
|
||||
i := list[0]
|
||||
if i.Counter != 1 {
|
||||
t.Errorf("Expected counter 1, got %d", i.Counter)
|
||||
}
|
||||
if i.Status != 200 {
|
||||
t.Errorf("Expected status 200, got %d", i.Status)
|
||||
}
|
||||
if i.Method != "GET" {
|
||||
t.Errorf("Expected method GET, got %s", i.Method)
|
||||
}
|
||||
if i.Timestamp != "2026-02-15 12:00:01.555" {
|
||||
t.Errorf("Expected timestamp 2026-02-15 12:00:01.555, got %s", i.Timestamp)
|
||||
}
|
||||
if i.Path != "/setup" {
|
||||
t.Errorf("Expected path /setup, got %s", i.Path)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_by_since", func(t *testing.T) {
|
||||
// session1 has 2026-02-15 12:00:01.555 and 12:00:02.000
|
||||
// session2 has 2026-02-15 13:00:05.000
|
||||
list, err := r.ListInteractions("", "", "2026-02-15 12:30:00")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
if len(list) != 1 {
|
||||
t.Errorf("Expected 1 interaction since 12:30:00, got %d", len(list))
|
||||
}
|
||||
if list[0].Session != session2 {
|
||||
t.Errorf("Expected session2, got %s", list[0].Session)
|
||||
}
|
||||
|
||||
list, err = r.ListInteractions("", "", "2026-02-15 12:00:01.600")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
// Should include 12:00:02.000 and 13:00:05.000
|
||||
if len(list) != 2 {
|
||||
t.Errorf("Expected 2 interactions since 12:00:01.600, got %d", len(list))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestRecorder_GetInteractionContent(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-content-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
relPath := filepath.Join(r.SessionID, "self", "test", "0001-GET.http")
|
||||
fullPath := filepath.Join(tmpDir, "interactions", relPath)
|
||||
os.MkdirAll(filepath.Dir(fullPath), 0755)
|
||||
|
||||
expectedContent := "test content"
|
||||
os.WriteFile(fullPath, []byte(expectedContent), 0644)
|
||||
|
||||
content, err := r.GetInteractionContent(relPath)
|
||||
if err != nil {
|
||||
t.Fatalf("GetInteractionContent failed: %v", err)
|
||||
}
|
||||
|
||||
if string(content) != expectedContent {
|
||||
t.Errorf("Expected %s, got %s", expectedContent, string(content))
|
||||
}
|
||||
|
||||
_, err = r.GetInteractionContent("non-existent")
|
||||
if err == nil {
|
||||
t.Error("Expected error for non-existent file, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorder_Record_FullExchange(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-full-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
|
||||
req := &http.Request{
|
||||
Method: "POST",
|
||||
URL: &url.URL{
|
||||
Path: "/test",
|
||||
},
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader("request body")),
|
||||
}
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
|
||||
res := &http.Response{
|
||||
StatusCode: 200,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(strings.NewReader("response body")),
|
||||
Request: req,
|
||||
}
|
||||
res.Header.Set("Content-Type", "application/json")
|
||||
|
||||
err = r.Record("self", req, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Record failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify file content
|
||||
interactionsDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "test")
|
||||
files, _ := os.ReadDir(interactionsDir)
|
||||
if len(files) == 0 {
|
||||
t.Fatal("No recording file found")
|
||||
}
|
||||
|
||||
content, _ := os.ReadFile(filepath.Join(interactionsDir, files[0].Name()))
|
||||
contentStr := string(content)
|
||||
|
||||
if !strings.Contains(contentStr, "request body") {
|
||||
t.Error("Recording does not contain request body")
|
||||
}
|
||||
if !strings.Contains(contentStr, "Response: 200 OK") {
|
||||
t.Error("Recording does not contain response status")
|
||||
}
|
||||
if !strings.Contains(contentStr, "response body") {
|
||||
t.Error("Recording does not contain response body")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorder_Record_BinaryResponse(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-binary-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
|
||||
req := &http.Request{
|
||||
Method: "GET",
|
||||
URL: &url.URL{Path: "/image"},
|
||||
}
|
||||
|
||||
res := &http.Response{
|
||||
StatusCode: 200,
|
||||
Header: make(http.Header),
|
||||
Body: io.NopCloser(bytes.NewBuffer([]byte{0x00, 0x01, 0x02, 0x03})),
|
||||
Request: req,
|
||||
}
|
||||
res.Header.Set("Content-Type", "image/png")
|
||||
|
||||
err = r.Record("self", req, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Record failed: %v", err)
|
||||
}
|
||||
|
||||
interactionsDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "image")
|
||||
files, _ := os.ReadDir(interactionsDir)
|
||||
content, _ := os.ReadFile(filepath.Join(interactionsDir, files[0].Name()))
|
||||
contentStr := string(content)
|
||||
|
||||
if !strings.Contains(contentStr, "[Binary response body: 4 bytes]") {
|
||||
t.Error("Recording does not correctly report binary response")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecorder_ListInteractions_FullTimestamp(t *testing.T) {
|
||||
tmpDir, err := os.MkdirTemp("", "recorder-full-ts-test")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
r := NewRecorder(tmpDir)
|
||||
sessionID := "20260215-100000-12345"
|
||||
r.SessionID = sessionID
|
||||
|
||||
// Create some dummy recordings
|
||||
basePath := filepath.Join(tmpDir, "interactions", sessionID, "self", "test")
|
||||
os.MkdirAll(basePath, 0755)
|
||||
|
||||
files := []string{
|
||||
"0001-10-00-01.000-GET.http",
|
||||
"0002-11-00-00.000-GET.http",
|
||||
}
|
||||
|
||||
for _, f := range files {
|
||||
os.WriteFile(filepath.Join(basePath, f), []byte("test"), 0644)
|
||||
}
|
||||
|
||||
t.Run("Check_Full_Timestamp_Display", func(t *testing.T) {
|
||||
interactions, err := r.ListInteractions(sessionID, "", "")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
if len(interactions) != 2 {
|
||||
t.Fatalf("Expected 2 interactions, got %d", len(interactions))
|
||||
}
|
||||
|
||||
expectedTS := "2026-02-15 10:00:01.000"
|
||||
if interactions[0].Timestamp != expectedTS {
|
||||
t.Errorf("Expected timestamp %s, got %s", expectedTS, interactions[0].Timestamp)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_By_Full_Date_Time", func(t *testing.T) {
|
||||
// Filter for interactions since 10:30:00 on that day
|
||||
interactions, err := r.ListInteractions(sessionID, "", "2026-02-15 10:30:00")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
if len(interactions) != 1 {
|
||||
t.Fatalf("Expected 1 interaction, got %d", len(interactions))
|
||||
}
|
||||
|
||||
if interactions[0].ID != "0002-11-00-00.000-GET.http" {
|
||||
t.Errorf("Expected 0002-..., got %s", interactions[0].ID)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Filter_By_Date_Only", func(t *testing.T) {
|
||||
// Filter for interactions since the day before
|
||||
interactions, err := r.ListInteractions(sessionID, "", "2026-02-14")
|
||||
if err != nil {
|
||||
t.Fatalf("ListInteractions failed: %v", err)
|
||||
}
|
||||
|
||||
if len(interactions) != 2 {
|
||||
t.Fatalf("Expected 2 interactions, got %d", len(interactions))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user