package proxy import ( "archive/tar" "bytes" "compress/gzip" "encoding/json" "fmt" "io" "net/http" "net/url" "os" "path/filepath" "regexp" "strings" "testing" ) func TestRecorder_Record_Structure(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Compile default patterns for i := range r.Patterns { re, _ := regexp.Compile(r.Patterns[i].Regexp) r.Patterns[i].compiled = re } tests := []struct { name string category string path string expected string // Expected subdirectory after interactions/{sessionID}/{category}/ }{ { name: "root_path", category: "self", path: "/", expected: "root", }, { name: "simple_path", category: "self", path: "/setup/info", expected: "setup/info", }, { name: "path_with_ip", category: "self", path: "/setup/info/192.0.2.100", expected: "setup/info/{ip}", }, { name: "upstream_path", category: "upstream", path: "/v1/playback/station/s123", expected: "v1/playback/station/s123", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { req := &http.Request{ Method: "GET", URL: &url.URL{ Path: tt.path, }, Header: make(http.Header), } err := r.Record(tt.category, req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } expectedDir := filepath.Join(tmpDir, "interactions", r.SessionID, tt.category, tt.expected) if _, err := os.Stat(expectedDir); os.IsNotExist(err) { t.Errorf("Expected directory %s does not exist", expectedDir) } // Check if file was created files, _ := os.ReadDir(expectedDir) if len(files) == 0 { t.Errorf("No files created in %s", expectedDir) } for _, f := range files { if !strings.Contains(f.Name(), "-GET.http") { t.Errorf("Unexpected filename: %s", f.Name()) } // Verify prefix is 4 digits if len(f.Name()) < 5 || !isDigit(f.Name()[0]) || !isDigit(f.Name()[1]) || !isDigit(f.Name()[2]) || !isDigit(f.Name()[3]) || f.Name()[4] != '-' { t.Errorf("Filename %s does not have correct 0000- prefix", f.Name()) } // Verify Host header is present content, _ := os.ReadFile(filepath.Join(expectedDir, f.Name())) if !strings.Contains(string(content), "Host: ") { t.Errorf("Recorded file does not contain Host header:\n%s", string(content)) } } }) } } func TestRecorder_Record_Sanitization(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-sanitization-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Add a custom pattern r.Patterns = append(r.Patterns, PathPattern{ Name: "CustomDeviceID", Regexp: `^A81B\w{8}$`, Replacement: "{deviceId}", }) // Compile all patterns for i := range r.Patterns { re, _ := regexp.Compile(r.Patterns[i].Regexp) r.Patterns[i].compiled = re } req := &http.Request{ Method: "GET", URL: &url.URL{ Path: "/info/192.0.2.100/AABBCCDDEEFF", }, Header: make(http.Header), } req.Header.Set("X-Device", "AABBCCDDEEFF") err = r.Record("self", req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } expectedDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "info", "{ip}", "{device_id}") if _, err := os.Stat(expectedDir); os.IsNotExist(err) { t.Errorf("Expected directory %s does not exist", expectedDir) } files, _ := os.ReadDir(expectedDir) if len(files) == 0 { t.Fatalf("No files created in %s", expectedDir) } content, _ := os.ReadFile(filepath.Join(expectedDir, files[0].Name())) contentStr := string(content) if !strings.Contains(contentStr, "### GET /info/{{ip}}/{{device_id}}") { t.Errorf("Expected sanitized comment in .http file, got:\n%s", contentStr) } if !strings.Contains(contentStr, "GET /info/{{ip}}/{{device_id}}") { t.Errorf("Expected sanitized URL in .http file, got:\n%s", contentStr) } if !strings.Contains(contentStr, "X-Device: {{device_id}}") { t.Errorf("Expected sanitized Header in .http file, got:\n%s", contentStr) } } func TestRecorder_Record_Sanitization_Account(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-sanitization-account-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Use default patterns which now include AccountID r.Patterns = DefaultPatterns() // Compile all patterns for i := range r.Patterns { re, _ := regexp.Compile(r.Patterns[i].Regexp) r.Patterns[i].compiled = re } accountID := "1234567" req := &http.Request{ Method: "GET", URL: &url.URL{ Path: "/marge/accounts/" + accountID + "/full", }, Header: make(http.Header), } err = r.Record("self", req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } expectedDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "marge", "accounts", "{accountId}", "full") if _, err := os.Stat(expectedDir); os.IsNotExist(err) { t.Errorf("Expected directory %s does not exist", expectedDir) } files, _ := os.ReadDir(expectedDir) if len(files) == 0 { t.Fatalf("No files created in %s", expectedDir) } content, _ := os.ReadFile(filepath.Join(expectedDir, files[0].Name())) contentStr := string(content) if !strings.Contains(contentStr, "### GET /marge/accounts/{{accountId}}/full") { t.Errorf("Expected sanitized comment in .http file, got:\n%s", contentStr) } if !strings.Contains(contentStr, "GET /marge/accounts/{{accountId}}/full") { t.Errorf("Expected sanitized URL in .http file, got:\n%s", contentStr) } if !strings.Contains(contentStr, "// accountId: "+accountID) { t.Errorf("Expected accountId comment in .http file, got:\n%s", contentStr) } } func TestRecorder_Record_Redaction(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-redaction-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) r.Redact = true req := &http.Request{ Method: "GET", URL: &url.URL{ Path: "/test", }, Header: make(http.Header), } req.Header.Set("Authorization", "Bearer sensitive-token") req.Header.Set("Cookie", "session=secret") req.Header.Set("X-Normal", "public-info") err = r.Record("self", req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } expectedDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "test") files, _ := os.ReadDir(expectedDir) content, _ := os.ReadFile(filepath.Join(expectedDir, files[0].Name())) contentStr := string(content) if !strings.Contains(contentStr, "Authorization: [REDACTED]") { t.Errorf("Expected Authorization header to be redacted, got:\n%s", contentStr) } if strings.Contains(contentStr, "sensitive-token") { t.Errorf("Sensitive token still present in content:\n%s", contentStr) } if !strings.Contains(contentStr, "Cookie: [REDACTED]") { t.Errorf("Expected Cookie header to be redacted, got:\n%s", contentStr) } if !strings.Contains(contentStr, "X-Normal: public-info") { t.Errorf("Expected normal header to be present, got:\n%s", contentStr) } } func isDigit(c byte) bool { return c >= '0' && c <= '9' } func TestRecorder_IncreasingPrefix(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-prefix-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Compile default patterns for i := range r.Patterns { re, _ := regexp.Compile(r.Patterns[i].Regexp) r.Patterns[i].compiled = re } req := &http.Request{ Method: "GET", URL: &url.URL{ Path: "/test", }, Header: make(http.Header), } for i := 1; i <= 3; i++ { err := r.Record("self", req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } } expectedDir := filepath.Join(tmpDir, "interactions", r.SessionID, "self", "test") files, _ := os.ReadDir(expectedDir) if len(files) != 3 { t.Fatalf("Expected 3 files, got %d", len(files)) } expectedPrefixes := []string{"0001-", "0002-", "0003-"} for i, f := range files { if !strings.HasPrefix(f.Name(), expectedPrefixes[i]) { t.Errorf("File %d: expected prefix %s, got %s", i, expectedPrefixes[i], f.Name()) } } } func TestRecorder_EnvFile(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") tmpDir, err := os.MkdirTemp("", "recorder-env-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Compile default patterns for i := range r.Patterns { re, _ := regexp.Compile(r.Patterns[i].Regexp) r.Patterns[i].compiled = re } req := &http.Request{ Method: "GET", URL: &url.URL{ Path: "/info/192.0.2.100", }, Header: make(http.Header), } err = r.Record("self", req, nil) if err != nil { t.Fatalf("Record failed: %v", err) } envFile := filepath.Join(tmpDir, "interactions", r.SessionID, "http-client.env.json") if _, err := os.Stat(envFile); os.IsNotExist(err) { t.Fatalf("Expected env file %s does not exist", envFile) } data, _ := os.ReadFile(envFile) var content map[string]map[string]string if err := json.Unmarshal(data, &content); err != nil { t.Fatalf("Failed to unmarshal env file: %v", err) } if content["session"]["ip"] != "192.0.2.100" { t.Errorf("Expected ip to be 192.0.2.100, got %s", content["session"]["ip"]) } } func TestRecorder_GetInteractionStats(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") 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) { t.Setenv("RECORDER_ASYNC", "false") 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_DeleteAndCleanup(t *testing.T) { tmpDir, err := os.MkdirTemp("", "recorder-delete-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) // Create 15 dummy sessions for i := 1; i <= 15; i++ { // Use a format that sorts correctly: YYYYMMDD-HHmmss-PID sessionID := fmt.Sprintf("20260215-12%02d00-12345", i) sessionDir := filepath.Join(tmpDir, "interactions", sessionID) os.MkdirAll(sessionDir, 0755) os.WriteFile(filepath.Join(sessionDir, "test.http"), []byte("test"), 0644) } t.Run("Delete_specific_session", func(t *testing.T) { sessionToDelete := "20260215-120500-12345" err := r.DeleteSession(sessionToDelete) if err != nil { t.Fatalf("DeleteSession failed: %v", err) } if _, err := os.Stat(filepath.Join(tmpDir, "interactions", sessionToDelete)); !os.IsNotExist(err) { t.Errorf("Session %s still exists after deletion", sessionToDelete) } }) t.Run("Cleanup_sessions", func(t *testing.T) { err := r.CleanupSessions(10) if err != nil { t.Fatalf("CleanupSessions failed: %v", err) } entries, _ := os.ReadDir(filepath.Join(tmpDir, "interactions")) if len(entries) != 10 { t.Errorf("Expected 10 sessions to remain, got %d", len(entries)) } // Ensure newest sessions are kept // We created 1 to 15, deleted 5. Remaining: 1-4, 6-15 (14 sessions) // Cleanup(10) should keep 15, 14, 13, 12, 11, 10, 9, 8, 7, 6. expectedRemaining := []string{ "20260215-120600-12345", "20260215-120700-12345", "20260215-120800-12345", "20260215-120900-12345", "20260215-121000-12345", "20260215-121100-12345", "20260215-121200-12345", "20260215-121300-12345", "20260215-121400-12345", "20260215-121500-12345", } for _, sessionID := range expectedRemaining { if _, err := os.Stat(filepath.Join(tmpDir, "interactions", sessionID)); os.IsNotExist(err) { t.Errorf("Expected session %s to remain, but it was deleted", sessionID) } } // Check one that should be deleted if _, err := os.Stat(filepath.Join(tmpDir, "interactions", "20260215-120100-12345")); !os.IsNotExist(err) { t.Errorf("Session 20260215-120100-12345 should have been cleaned up") } }) } 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_ArchiveSession(t *testing.T) { tmpDir, _ := os.MkdirTemp("", "archive-test") defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) sessionID := "test-session-123" sessionDir := filepath.Join(tmpDir, "interactions", sessionID) os.MkdirAll(filepath.Join(sessionDir, "category1"), 0755) os.WriteFile(filepath.Join(sessionDir, "category1", "file1.http"), []byte("content1"), 0644) os.WriteFile(filepath.Join(sessionDir, "file2.http"), []byte("content2"), 0644) var buf bytes.Buffer err := r.ArchiveSession(sessionID, &buf) if err != nil { t.Fatalf("ArchiveSession failed: %v", err) } if buf.Len() == 0 { t.Fatal("Archive buffer is empty") } // Verify it's a valid tar.gz gr, err := gzip.NewReader(&buf) if err != nil { t.Fatalf("Failed to create gzip reader: %v", err) } defer gr.Close() tr := tar.NewReader(gr) files := make(map[string]string) for { header, err := tr.Next() if err == io.EOF { break } if err != nil { t.Fatalf("Failed to read tar: %v", err) } if header.Typeflag == tar.TypeReg { var b bytes.Buffer io.Copy(&b, tr) files[header.Name] = b.String() } } if len(files) != 2 { t.Errorf("Expected 2 files in archive, got %d", len(files)) } if files["category1/file1.http"] != "content1" { t.Errorf("Unexpected content for category1/file1.http: %s", files["category1/file1.http"]) } if files["file2.http"] != "content2" { t.Errorf("Unexpected content for file2.http: %s", files["file2.http"]) } } func TestRecorder_Record_FullExchange(t *testing.T) { t.Setenv("RECORDER_ASYNC", "false") 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) { t.Setenv("RECORDER_ASYNC", "false") 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)) } }) } func TestRecorder_NewFilenameFormat_WithDate(t *testing.T) { tmpDir, err := os.MkdirTemp("", "recorder-new-format-test") if err != nil { t.Fatalf("failed to create temp dir: %v", err) } defer os.RemoveAll(tmpDir) r := NewRecorder(tmpDir) sessionID := "20260223-150000-12345" r.SessionID = sessionID // Create recordings with the new format that includes date in filename basePath := filepath.Join(tmpDir, "interactions", sessionID, "self", "test") os.MkdirAll(basePath, 0755) files := []string{ "0047-20260223-215306.128-GET.http", "0048-20260223-215306.417-POST.http", "0049-20260223-080034.500-GET.http", "0050-20260223-080034.507-PUT.http", } for _, f := range files { os.WriteFile(filepath.Join(basePath, f), []byte("test content"), 0644) } t.Run("Parse_New_Format_Timestamps", func(t *testing.T) { interactions, err := r.ListInteractions(sessionID, "", "") if err != nil { t.Fatalf("ListInteractions failed: %v", err) } if len(interactions) != 4 { t.Fatalf("Expected 4 interactions, got %d", len(interactions)) } // Check that timestamps include both date and time from filename expectedTimestamps := []string{ "2026-02-23 21:53:06.128", "2026-02-23 21:53:06.417", "2026-02-23 08:00:34.500", "2026-02-23 08:00:34.507", } for i, interaction := range interactions { if interaction.Timestamp != expectedTimestamps[i] { t.Errorf("Expected timestamp %s, got %s for interaction %d", expectedTimestamps[i], interaction.Timestamp, i) } } }) t.Run("Parse_Methods_From_New_Format", func(t *testing.T) { interactions, err := r.ListInteractions(sessionID, "", "") if err != nil { t.Fatalf("ListInteractions failed: %v", err) } expectedMethods := []string{"GET", "POST", "GET", "PUT"} for i, interaction := range interactions { if interaction.Method != expectedMethods[i] { t.Errorf("Expected method %s, got %s for interaction %d", expectedMethods[i], interaction.Method, i) } } }) t.Run("Filter_By_New_Format_Timestamp", func(t *testing.T) { // Filter for interactions after 10:00:00 on that day interactions, err := r.ListInteractions(sessionID, "", "2026-02-23 10:00:00") if err != nil { t.Fatalf("ListInteractions failed: %v", err) } // Should get the two evening interactions (21:53:06.xxx) if len(interactions) != 2 { t.Fatalf("Expected 2 interactions after 10:00:00, got %d", len(interactions)) } for _, interaction := range interactions { if !strings.Contains(interaction.Timestamp, "21:53:06") { t.Errorf("Expected evening timestamp, got %s", interaction.Timestamp) } } }) t.Run("GetFullTimestamp_New_Format", func(t *testing.T) { // Test the getFullTimestamp function directly fullTS := r.getFullTimestamp(sessionID, "0047-20260223-215306.128-GET.http") expected := "2026-02-23-21-53-06.128" if fullTS != expected { t.Errorf("Expected full timestamp %s, got %s", expected, fullTS) } }) }