返回 DeepSeek-Reasonix
sink_test.go
根目录 / internal / telemetry / sink_test.go
1 package telemetry
2
3 import (
4 "encoding/json"
5 "errors"
6 "os"
7 "path/filepath"
8 "strings"
9 "testing"
10
11 "reasonix/internal/event"
12 "reasonix/internal/evidence"
13 "reasonix/internal/provider"
14 )
15
16 type readinessSink struct {
17 events int
18 audits int
19 recovery int
20 }
21
22 func (s *readinessSink) Emit(event.Event) { s.events++ }
23 func (s *readinessSink) RecordReadinessAudit(evidence.ReadinessAudit) {
24 s.audits++
25 }
26 func (s *readinessSink) RecordProtocolRecovery(event.ProtocolRecoveryAudit) {
27 s.recovery++
28 }
29
30 func TestSinkWritesOnlyWhitelistedContentFreeCounters(t *testing.T) {
31 home := t.TempDir()
32 reporter := &Reporter{
33 home: home,
34 version: "v1.20.0",
35 static: []Counter{
36 {Signal: "client_surface", Bucket: "cli", Count: 1},
37 {Signal: "cli_mode", Bucket: "run", Count: 1},
38 },
39 }
40 inner := &readinessSink{}
41 sink := reporter.Wrap(inner)
42 secret := "PRIVATE_PROMPT_TOKEN_123"
43 sink.Emit(event.Event{Kind: event.TurnStarted})
44 sink.Emit(event.Event{Kind: event.Text, Text: secret})
45 sink.Emit(event.Event{Kind: event.Message, Text: secret, Reasoning: secret})
46 sink.Emit(event.Event{Kind: event.Usage, Usage: &provider.Usage{
47 FinishReason: "stop", CacheHitTokens: 90, CacheMissTokens: 10,
48 }})
49 sink.Emit(event.Event{Kind: event.ToolResult, Tool: event.Tool{
50 Name: secret, Args: secret, Output: secret, Err: "permission denied: " + secret,
51 }})
52 event.RecordProtocolRecovery(sink, event.ProtocolRecoveryAudit{Kind: event.ProtocolRecoveryMissingReasoningRetryReplaced})
53 sink.Emit(event.Event{Kind: event.TurnDone, Err: &provider.APIError{
54 Provider: secret, Status: 429, Body: secret, TraceID: secret,
55 }})
56 event.RecordReadinessAudit(sink, evidence.ReadinessAudit{})
57
58 entries, err := os.ReadDir(filepath.Join(home, pendingDirName))
59 if err != nil || len(entries) != 1 {
60 t.Fatalf("pending files = %d, err = %v", len(entries), err)
61 }
62 b, err := os.ReadFile(filepath.Join(home, pendingDirName, entries[0].Name()))
63 if err != nil {
64 t.Fatal(err)
65 }
66 if strings.Contains(string(b), secret) {
67 t.Fatalf("pending payload leaked private content: %s", b)
68 }
69 var payload pendingPayload
70 if err := json.Unmarshal(b, &payload); err != nil {
71 t.Fatal(err)
72 }
73 got := map[string]string{}
74 for _, counter := range payload.Counters {
75 got[counter.Signal] = counter.Bucket
76 }
77 for signal, bucket := range map[string]string{
78 "client_surface": "cli",
79 "cli_mode": "run",
80 "turns": "count",
81 "finish_reason": "stop",
82 "cache_hit": "90_100",
83 "tool_error": "permission",
84 "provider_error": "rate_limit",
85 "cli_exit": "error",
86 "tool_call_reasoning_recovery": "missing_reasoning_retry_replaced_response",
87 } {
88 if got[signal] != bucket {
89 t.Errorf("%s bucket = %q, want %q", signal, got[signal], bucket)
90 }
91 }
92 if inner.events != 6 || inner.audits != 1 || inner.recovery != 1 {
93 t.Fatalf("forwarding events=%d audits=%d recovery=%d", inner.events, inner.audits, inner.recovery)
94 }
95 }
96
97 func TestCleanupRemovesPendingQueueOnly(t *testing.T) {
98 home := t.TempDir()
99 if err := appendPending(home, pendingPayload{
100 Version: "v1.20.0", OS: "linux", Counters: []Counter{{Signal: "turns", Bucket: "count", Count: 1}},
101 }); err != nil {
102 t.Fatal(err)
103 }
104 idPath := filepath.Join(home, "cli-telemetry-install-id")
105 if err := os.WriteFile(idPath, []byte(strings.Repeat("a", 32)), 0o600); err != nil {
106 t.Fatal(err)
107 }
108 if err := Cleanup(home); err != nil {
109 t.Fatal(err)
110 }
111 if _, err := os.Stat(filepath.Join(home, pendingDirName)); !errors.Is(err, os.ErrNotExist) {
112 t.Fatalf("pending directory still exists: %v", err)
113 }
114 if _, err := os.Stat(idPath); err != nil {
115 t.Fatalf("install id should remain stable after opt-out cleanup: %v", err)
116 }
117 }
118
119 func TestEnvironmentOptOutRemovesPendingQueue(t *testing.T) {
120 clearPolicyEnv(t)
121 home := t.TempDir()
122 if err := appendPending(home, pendingPayload{
123 Version: "v1.20.0", OS: "linux", Counters: []Counter{{Signal: "turns", Bucket: "count", Count: 1}},
124 }); err != nil {
125 t.Fatal(err)
126 }
127 t.Setenv("DO_NOT_TRACK", "1")
128 if reporter := Start(Options{Mode: "on", Version: "v1.20.0", HomeDir: home, Interactive: true}); reporter != nil {
129 t.Fatal("environment opt-out unexpectedly started telemetry")
130 }
131 if _, err := os.Stat(filepath.Join(home, pendingDirName)); !errors.Is(err, os.ErrNotExist) {
132 t.Fatalf("environment opt-out did not remove pending queue: %v", err)
133 }
134 }
135
135 lines GO