返回 DeepSeek-Reasonix
scroll_diagnostics_export_test.go
根目录 / desktop / scroll_diagnostics_export_test.go
1 package main
2
3 import (
4 "archive/zip"
5 "bytes"
6 "crypto/sha256"
7 "encoding/json"
8 "fmt"
9 "io"
10 "strings"
11 "testing"
12 )
13
14 func validScrollDiagnosticsPayload(t *testing.T) string {
15 t.Helper()
16 payload := map[string]any{
17 "schemaVersion": 2,
18 "manifest": map[string]any{
19 "reportId": "0123456789abcdef0123456789abcdef",
20 "createdAt": "2026-08-19T03:00:00.000Z",
21 "buildCommit": "5386a938",
22 "buildChannel": "test",
23 "platform": "windows",
24 "userAgent": "Mozilla/5.0 diagnostic-test",
25 "devicePixelRatio": 1.25,
26 "viewportWidth": 1440,
27 "viewportHeight": 900,
28 "reducedMotion": false,
29 "transcriptWidth": 1180,
30 "contentWidth": 960,
31 "fontSize": 14,
32 "lineHeight": 23.52,
33 "processFoldPreference": "auto",
34 "reasoningDisplayMode": "summary",
35 },
36 "summary": map[string]any{
37 "durationMs": 1200,
38 "eventCount": 5,
39 "droppedEventCount": 0,
40 "markerCount": 1,
41 },
42 "events": []any{
43 map[string]any{"t": 0, "type": "start"},
44 map[string]any{"t": 350, "type": "row-measure", "rowIndex": 44, "rowKind": "answer", "estimatedSize": 1800, "previousSize": 1800, "measuredSize": 420, "sizeDelta": -1380, "contentRevision": 3, "foldState": "closed", "disclosureCount": 1},
45 map[string]any{"t": 500, "type": "mark"},
46 map[string]any{"t": 700, "type": "scroll-state", "source": "jump-bottom", "previousMode": "manual", "mode": "tail-follow", "atBottom": true, "scrollable": true, "tailCommand": true},
47 map[string]any{"t": 1200, "type": "stop"},
48 },
49 }
50 data, err := json.Marshal(payload)
51 if err != nil {
52 t.Fatal(err)
53 }
54 return string(data)
55 }
56
57 func TestBuildScrollDiagnosticsZip(t *testing.T) {
58 t.Parallel()
59 data, err := buildScrollDiagnosticsZip(validScrollDiagnosticsPayload(t))
60 if err != nil {
61 t.Fatalf("build diagnostic zip: %v", err)
62 }
63
64 zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
65 if err != nil {
66 t.Fatalf("open diagnostic zip: %v", err)
67 }
68 wantNames := []string{"manifest.json", "summary.json", "scroll-events.jsonl", "sha256.txt"}
69 if len(zr.File) != len(wantNames) {
70 t.Fatalf("zip entries = %d, want %d", len(zr.File), len(wantNames))
71 }
72 for i, file := range zr.File {
73 if file.Name != wantNames[i] {
74 t.Fatalf("zip entry %d = %q, want %q", i, file.Name, wantNames[i])
75 }
76 if strings.Contains(file.Name, "..") || strings.HasPrefix(file.Name, "/") {
77 t.Fatalf("unsafe zip entry %q", file.Name)
78 }
79 }
80
81 eventsFile := zr.File[2]
82 r, err := eventsFile.Open()
83 if err != nil {
84 t.Fatal(err)
85 }
86 events, err := io.ReadAll(r)
87 _ = r.Close()
88 if err != nil {
89 t.Fatal(err)
90 }
91 lines := strings.Split(strings.TrimSpace(string(events)), "\n")
92 if len(lines) != 5 {
93 t.Fatalf("event lines = %d, want 5", len(lines))
94 }
95 if !strings.Contains(lines[1], `"type":"row-measure"`) || strings.Contains(lines[1], "rowKey") {
96 t.Fatalf("second event = %q, want privacy-safe row measurement", lines[1])
97 }
98 if !strings.Contains(lines[2], `"type":"mark"`) {
99 t.Fatalf("third event = %q, want mark", lines[2])
100 }
101
102 checksumReader, err := zr.File[3].Open()
103 if err != nil {
104 t.Fatal(err)
105 }
106 checksum, err := io.ReadAll(checksumReader)
107 _ = checksumReader.Close()
108 if err != nil {
109 t.Fatal(err)
110 }
111 digest := sha256.Sum256(events)
112 wantChecksum := fmt.Sprintf("%x scroll-events.jsonl", digest)
113 if strings.TrimSpace(string(checksum)) != wantChecksum {
114 t.Fatalf("event checksum = %q, want %q", strings.TrimSpace(string(checksum)), wantChecksum)
115 }
116 }
117
118 func TestBuildScrollDiagnosticsZipRejectsUnsafePayloads(t *testing.T) {
119 t.Parallel()
120 tests := []struct {
121 name string
122 mutate func(map[string]any)
123 }{
124 {
125 name: "old schema version",
126 mutate: func(payload map[string]any) {
127 payload["schemaVersion"] = 1
128 },
129 },
130 {
131 name: "unknown content field",
132 mutate: func(payload map[string]any) {
133 payload["transcriptText"] = "PRIVATE_TRANSCRIPT_CANARY"
134 },
135 },
136 {
137 name: "unknown event field",
138 mutate: func(payload map[string]any) {
139 events := payload["events"].([]any)
140 events[0].(map[string]any)["rowKey"] = "raw-row-key"
141 },
142 },
143 {
144 name: "row measurement leaks row key",
145 mutate: func(payload map[string]any) {
146 events := payload["events"].([]any)
147 events[1].(map[string]any)["rowKey"] = "raw-row-key"
148 },
149 },
150 {
151 name: "invalid report id",
152 mutate: func(payload map[string]any) {
153 payload["manifest"].(map[string]any)["reportId"] = "C:\\Users\\private"
154 },
155 },
156 {
157 name: "too many events",
158 mutate: func(payload map[string]any) {
159 events := make([]any, maxScrollDiagnosticEvents+1)
160 for i := range events {
161 events[i] = map[string]any{"t": i, "type": "scroll"}
162 }
163 payload["events"] = events
164 payload["summary"].(map[string]any)["eventCount"] = len(events)
165 },
166 },
167 }
168
169 for _, test := range tests {
170 t.Run(test.name, func(t *testing.T) {
171 var payload map[string]any
172 if err := json.Unmarshal([]byte(validScrollDiagnosticsPayload(t)), &payload); err != nil {
173 t.Fatal(err)
174 }
175 test.mutate(payload)
176 data, err := json.Marshal(payload)
177 if err != nil {
178 t.Fatal(err)
179 }
180 if _, err := buildScrollDiagnosticsZip(string(data)); err == nil {
181 t.Fatal("expected unsafe payload to be rejected")
182 } else if strings.Contains(err.Error(), "PRIVATE_TRANSCRIPT_CANARY") || strings.Contains(err.Error(), "private") {
183 t.Fatalf("validation error exposed payload content: %q", err)
184 }
185 })
186 }
187 }
188
188 lines GO