| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "os" |
| 5 | "path/filepath" |
| 6 | "strings" |
| 7 | "testing" |
| 8 | |
| 9 | "reasonix/internal/event" |
| 10 | "reasonix/internal/provider" |
| 11 | "reasonix/internal/session" |
| 12 | ) |
| 13 | |
| 14 | func recordCanonicalUsage(t *testing.T, tab *WorkspaceTab, requests int) { |
| 15 | t.Helper() |
| 16 | tab.sink.recordUsageTelemetry(event.Event{ |
| 17 | Usage: &provider.Usage{PromptTokens: 100, CompletionTokens: 40, TotalTokens: 140, RequestCount: requests}, |
| 18 | Pricing: &provider.Pricing{CacheHit: 1, Input: 2, Output: 3, Currency: "¥"}, |
| 19 | }) |
| 20 | } |
| 21 | |
| 22 | func TestCanonicalSessionTelemetrySurvivesSwitchingAway(t *testing.T) { |
| 23 | app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 24 | sourceRef := session.SessionRef{HostID: localDesktopHostID, SessionID: tab.SessionID} |
| 25 | |
| 26 | recordCanonicalUsage(t, tab, 3) |
| 27 | before := tab.telemetrySnapshot().Usage |
| 28 | if before.RequestCount != 3 || before.SessionCost <= 0 { |
| 29 | t.Fatalf("recorded usage = %+v", before) |
| 30 | } |
| 31 | |
| 32 | if _, err := app.OpenSession(target.Ref()); err != nil { |
| 33 | t.Fatalf("open other session: %v", err) |
| 34 | } |
| 35 | if got := tab.telemetrySnapshot().Usage; got.RequestCount != 0 { |
| 36 | t.Fatalf("other session inherited usage: %+v", got) |
| 37 | } |
| 38 | if _, err := app.OpenSession(sourceRef); err != nil { |
| 39 | t.Fatalf("reopen session: %v", err) |
| 40 | } |
| 41 | got := tab.telemetrySnapshot().Usage |
| 42 | if got.RequestCount != before.RequestCount || got.PromptTokens != before.PromptTokens || |
| 43 | got.CompletionTokens != before.CompletionTokens || got.SessionCost != before.SessionCost { |
| 44 | t.Fatalf("usage after switching back = %+v, want %+v", got, before) |
| 45 | } |
| 46 | } |
| 47 | |
| 48 | func TestCanonicalSessionTelemetryToleratesCorruptStore(t *testing.T) { |
| 49 | app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 50 | sourceRef := session.SessionRef{HostID: localDesktopHostID, SessionID: tab.SessionID} |
| 51 | recordCanonicalUsage(t, tab, 2) |
| 52 | |
| 53 | path := canonicalTelemetryPath(sourceRef.SessionID) |
| 54 | if path == "" { |
| 55 | t.Fatal("no telemetry path for a canonical session") |
| 56 | } |
| 57 | if err := os.WriteFile(path, []byte("{not json"), 0o600); err != nil { |
| 58 | t.Fatal(err) |
| 59 | } |
| 60 | if _, err := app.OpenSession(target.Ref()); err != nil { |
| 61 | t.Fatal(err) |
| 62 | } |
| 63 | if _, err := app.OpenSession(sourceRef); err != nil { |
| 64 | t.Fatalf("corrupt telemetry must not fail the switch: %v", err) |
| 65 | } |
| 66 | if got := tab.telemetrySnapshot().Usage.RequestCount; got != 0 { |
| 67 | t.Fatalf("corrupt store yielded request count %d, want 0", got) |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | func TestCanonicalTelemetryPathRejectsUnsafeIDs(t *testing.T) { |
| 72 | isolateDesktopUserDirs(t) |
| 73 | for _, id := range []string{"", " ", "../escape", "a/b", `a\b`, ".", "a\x00b", ".hidden", "trailing.", strings.Repeat("a", 256), "CON", "nul.x", "COM1", "a:b"} { |
| 74 | if got := canonicalTelemetryPath(id); got != "" { |
| 75 | t.Fatalf("canonicalTelemetryPath(%q) = %q, want empty", id, got) |
| 76 | } |
| 77 | } |
| 78 | if got := canonicalTelemetryPath("session-A"); filepath.Base(got) != "session-A.telemetry.json" { |
| 79 | t.Fatalf("path = %q", got) |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | func TestCanonicalSessionReadTelemetrySurvivesSwitchingAway(t *testing.T) { |
| 84 | app, tab, target, _, _ := canonicalWorkspaceOpenFixture(t) |
| 85 | sourceRef := session.SessionRef{HostID: localDesktopHostID, SessionID: tab.SessionID} |
| 86 | |
| 87 | tab.sink.recordReadTelemetry(event.Event{Tool: event.Tool{Name: "read_file", Args: `{"path":"README.md"}`}}) |
| 88 | if got := tab.telemetrySnapshot().ReadFiles; len(got) != 1 { |
| 89 | t.Fatalf("recorded read files = %+v", got) |
| 90 | } |
| 91 | if _, err := app.OpenSession(target.Ref()); err != nil { |
| 92 | t.Fatal(err) |
| 93 | } |
| 94 | if got := tab.telemetrySnapshot().ReadFiles; len(got) != 0 { |
| 95 | t.Fatalf("other session inherited reads: %+v", got) |
| 96 | } |
| 97 | if _, err := app.OpenSession(sourceRef); err != nil { |
| 98 | t.Fatal(err) |
| 99 | } |
| 100 | if got := tab.telemetrySnapshot().ReadFiles; len(got) != 1 || got[0].Path != "README.md" { |
| 101 | t.Fatalf("reads after switching back = %+v", got) |
| 102 | } |
| 103 | } |
| 104 |