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
|
package autotrack
import (
"os"
"path/filepath"
"strings"
"testing"
"time"
)
// TestEncodeLineByteParityWithPython pins the exact byte shape of a real line
// from ~/.local/share/ticktock/activity-2026-07-07.jsonl written by the
// Python daemon (json.dumps ", "/": " separators, key order, no escaping).
func TestEncodeLineByteParityWithPython(t *testing.T) {
r := Record{
Start: time.Date(2026, 7, 7, 8, 59, 19, 0, time.Local),
End: time.Date(2026, 7, 7, 9, 0, 9, 0, time.Local),
Secs: 49, App: "Trace",
}
want := `{"start": "2026-07-07T08:59:19", "end": "2026-07-07T09:00:09", "secs": 49, "app": "Trace", "title": "", "url": "", "domain": "", "idle": false}`
if got := r.EncodeLine(); got != want {
t.Errorf("byte mismatch:\n got %s\nwant %s", got, want)
}
}
func TestEncodeLineNoHTMLEscaping(t *testing.T) {
r := Record{
Start: time.Date(2026, 7, 7, 10, 0, 0, 0, time.Local),
End: time.Date(2026, 7, 7, 10, 1, 0, 0, time.Local),
Secs: 60, App: "Safari", Title: `R&D <notes> — café "x"`,
URL: "https://example.com/a?b=1&c=2", Domain: "example.com",
}
got := r.EncodeLine()
if !strings.Contains(got, `"title": "R&D <notes> — café \"x\""`) {
t.Errorf("ensure_ascii=False parity broken: %s", got)
}
if !strings.Contains(got, `"url": "https://example.com/a?b=1&c=2"`) {
t.Errorf("url should not be HTML-escaped: %s", got)
}
}
func TestWriteSegmentDropsShortAndAppends(t *testing.T) {
dir := filepath.Join(t.TempDir(), "data") // must be created by WriteSegment
start := time.Date(2026, 7, 7, 9, 0, 0, 0, time.Local)
short := &Segment{Ctx: Ctx{App: "Blip"}, Start: start, End: start.Add(10 * time.Second)}
if err := WriteSegment(dir, short, 15); err != nil {
t.Fatal(err)
}
if _, err := os.Stat(LogPath(dir, start)); !os.IsNotExist(err) {
t.Fatal("sub-minSecs segment must be dropped (no file written)")
}
long := &Segment{Ctx: Ctx{App: "Trace", Title: "doc"}, Start: start, End: start.Add(50 * time.Second)}
if err := WriteSegment(dir, long, 15); err != nil {
t.Fatal(err)
}
if err := WriteSegment(dir, long, 15); err != nil { // append, not truncate
t.Fatal(err)
}
b, err := os.ReadFile(LogPath(dir, start))
if err != nil {
t.Fatal(err)
}
lines := strings.Split(strings.TrimRight(string(b), "\n"), "\n")
if len(lines) != 2 {
t.Fatalf("want 2 appended lines, got %d: %q", len(lines), string(b))
}
want := `{"start": "2026-07-07T09:00:00", "end": "2026-07-07T09:00:50", "secs": 50, "app": "Trace", "title": "doc", "url": "", "domain": "", "idle": false}`
if lines[0] != want {
t.Errorf("line mismatch:\n got %s\nwant %s", lines[0], want)
}
if err := WriteSegment(dir, nil, 15); err != nil {
t.Errorf("nil segment must be a no-op, got %v", err)
}
}
|