-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathflowtracker_test.go
More file actions
194 lines (153 loc) · 4.54 KB
/
Copy pathflowtracker_test.go
File metadata and controls
194 lines (153 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package flowtracker
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
)
func NewServer() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/", Handler)
mw := NewMiddleware(WithExporter(&ConsoleExporter{}))
return mw(mux)
}
func TestDefaultExporter_OK(t *testing.T) {
// capture stdout
old := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
server := NewServer()
req := httptest.NewRequest(http.MethodGet, "/", nil)
rr := httptest.NewRecorder()
server.ServeHTTP(rr, req)
time.Sleep(500 * time.Millisecond)
// restore stdout
w.Close()
os.Stdout = old
// read logs
var buf bytes.Buffer
_, err := io.Copy(&buf, r)
if err != nil {
t.Error("Error should be nil")
}
logs := buf.String()
if !strings.Contains(logs, "FLOW_LOG: {\"trace_id\":\"trace-") {
t.Fatalf("expected log not found: %s", logs)
}
}
func Handler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// 1. Simulate some logic
processPayment(ctx)
// 2. Simulate a Database call
fetchUserProfile(ctx, "user_123")
// 3. Simulate external Microservice call
callShippingService(ctx)
w.Write([]byte("Order Processed"))
}
func processPayment(ctx context.Context) {
// Start a sub-span
ctx, finish := StartSpan(ctx, "Process Payment")
defer finish()
time.Sleep(50 * time.Millisecond) // Simulate work
AddTag(ctx, "currency", "USD")
}
func fetchUserProfile(ctx context.Context, userID string) {
ctx, finish := StartSpan(ctx, "DB: Select User")
defer finish()
AddTag(ctx, "db.query", "SELECT * FROM users...")
time.Sleep(120 * time.Millisecond) // Simulate DB latency
}
func callShippingService(ctx context.Context) {
ctx, finish := StartSpan(ctx, "HTTP: Shipping Service")
defer finish()
// Nested span example: Logic inside the external call preparation
func(innerCtx context.Context) {
_, end := StartSpan(innerCtx, "Calculate Weight")
defer end()
time.Sleep(10 * time.Millisecond)
}(ctx)
time.Sleep(200 * time.Millisecond) // Simulate network call
}
func TestEndToEndFlow_ConsoleExporter(t *testing.T) {
// 1. Capture Standard Output
// We replace os.Stdout with a pipe so we can read what the library prints.
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
// Ensure we restore stdout even if the test crashes
defer func() {
os.Stdout = oldStdout
}()
// 2. Define a simple handler that mimics real work
mockHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Simulate some logic with a span
ctx, finish := StartSpan(ctx, "BusinessLogic")
AddTag(ctx, "test.tag", "integration-check")
time.Sleep(10 * time.Millisecond) // Simulate work
finish()
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
// 3. Setup the Server with the Middleware
// We use the default NewMiddleware which uses the ConsoleExporter
mw := NewMiddleware()
server := httptest.NewServer(mw(mockHandler))
defer server.Close()
// 4. Make the HTTP Request
resp, err := http.Get(server.URL + "/test-endpoint")
if err != nil {
t.Fatalf("Failed to make request: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected 200 OK, got %d", resp.StatusCode)
}
// 5. WAIT for Async Export
// The middleware runs the export in a 'go func()'.
// We must sleep briefly to let that goroutine finish printing to stdout.
time.Sleep(50 * time.Millisecond)
// 6. Close the Write end of the pipe and read the output
w.Close()
var buf bytes.Buffer
io.Copy(&buf, r)
output := buf.String()
// 7. Verify the Output
t.Logf("Captured Output: %s", output)
if !strings.Contains(output, "FLOW_LOG:") {
t.Fatalf("Expected output to contain 'FLOW_LOG:', but got empty or wrong data.")
}
// 8. Validate the JSON structure
// Extract the JSON part after the prefix
jsonPart := strings.TrimSpace(strings.TrimPrefix(output, "FLOW_LOG:"))
var trace Trace
if err := json.Unmarshal([]byte(jsonPart), &trace); err != nil {
t.Fatalf("Failed to unmarshal JSON from logs: %v", err)
}
// 9. Assert Data Integrity
if trace.TraceID == "" {
t.Error("TraceID should not be empty")
}
// Expect 2 spans: The Root Span (GET /test-endpoint) + "BusinessLogic"
if len(trace.Spans) != 2 {
t.Errorf("Expected 2 spans, got %d", len(trace.Spans))
}
// Check if our specific tag exists
foundTag := false
for _, s := range trace.Spans {
if s.Tags != nil && s.Tags["test.tag"] == "integration-check" {
foundTag = true
break
}
}
if !foundTag {
t.Error("Did not find the expected 'test.tag' in the trace output")
}
}