Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

342 lines
9.5 KiB
Go

package parser
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"testing"
"github.com/klauspost/compress/zstd"
_ "github.com/mattn/go-sqlite3"
)
// parseZedAll parses every top-level thread in a Zed threads.db using the same
// per-thread primitives the provider uses (ListZedThreadMetas +
// parseZedThreadFromDB), reproducing the deleted ParseZedSessions
// whole-database free function for the retained parse tests.
func parseZedAll(dbPath, machine string) ([]ParseResult, error) {
info, err := os.Stat(dbPath)
if err != nil {
return nil, fmt.Errorf("stat %s: %w", dbPath, err)
}
conn, err := OpenZedDB(dbPath)
if err != nil {
return nil, err
}
defer conn.Close()
metas, err := ListZedThreadMetas(conn, dbPath)
if err != nil {
return nil, err
}
var out []ParseResult
for _, m := range metas {
result, err := parseZedThreadFromDB(conn, dbPath, m.RawID, machine, info)
if err != nil {
return nil, err
}
if result != nil {
out = append(out, *result)
}
}
return out, nil
}
func TestParseZedSessions_JSON(t *testing.T) {
dbPath := createZedThreadsDB(t, []zedTestThread{{
id: "10431c84-c47b-4e6c-b2df-f9f3b9ad025b",
summary: "WP Record Scaffold Generation",
createdAt: "2026-06-08T09:12:41.962819Z",
updatedAt: "2026-06-08T09:14:10.475149Z",
folderPaths: "/Users/alice/code/my-app",
dataType: "json",
data: []byte(`{
"model": {"model": "claude-opus-4", "provider": "anthropic"},
"request_token_usage": {
"req-1": {"input_tokens": 1000, "output_tokens": 200},
"req-2": {"input_tokens": 1500, "output_tokens": 300}
},
"messages": [
{"User": {"content": [{"Text": "Generate code"}]}},
{"Agent": {"content": [
{"Thinking": {"text": "Plan"}},
{"Text": "Done"},
{"ToolUse": {"id": "call_1", "name": "terminal", "input": {"command": "make test"}}}
], "tool_results": {"call_1": {"content": [{"Text": "ok"}]}}}}
]
}`),
}})
results, err := parseZedAll(dbPath, "local")
if err != nil {
t.Fatalf("ParseZedSessions: %v", err)
}
if len(results) != 1 {
t.Fatalf("len(results) = %d, want 1", len(results))
}
sess := results[0].Session
if sess.ID != "zed:10431c84-c47b-4e6c-b2df-f9f3b9ad025b" {
t.Fatalf("session ID = %q", sess.ID)
}
if sess.Project != "my_app" || sess.Cwd != "/Users/alice/code/my-app" {
t.Fatalf("project/cwd = %q/%q", sess.Project, sess.Cwd)
}
if sess.FirstMessage != "Generate code" || sess.MessageCount != 2 || sess.UserMessageCount != 1 {
t.Fatalf("session summary = %+v", sess)
}
if sess.File.Path != ZedSQLiteVirtualPath(dbPath, "10431c84-c47b-4e6c-b2df-f9f3b9ad025b") {
t.Fatalf("file path = %q", sess.File.Path)
}
if sess.TotalOutputTokens != 500 || sess.PeakContextTokens != 1500 {
t.Fatalf("tokens: total_out=%d (want 500), peak_ctx=%d (want 1500)", sess.TotalOutputTokens, sess.PeakContextTokens)
}
if !sess.HasTotalOutputTokens || !sess.HasPeakContextTokens {
t.Fatalf("token presence flags not set: has_total=%v, has_peak=%v", sess.HasTotalOutputTokens, sess.HasPeakContextTokens)
}
msgs := results[0].Messages
if msgs[0].Role != RoleUser || msgs[0].Content != "Generate code" {
t.Fatalf("user msg = %+v", msgs[0])
}
if msgs[1].Role != RoleAssistant || msgs[1].Content != "Done" || !msgs[1].HasThinking || !msgs[1].HasToolUse {
t.Fatalf("assistant msg = %+v", msgs[1])
}
if msgs[1].Model != "claude-opus-4" {
t.Fatalf("assistant model = %q, want %q", msgs[1].Model, "claude-opus-4")
}
if len(msgs[1].ToolCalls) != 1 || msgs[1].ToolCalls[0].ToolName != "terminal" {
t.Fatalf("tool calls = %+v", msgs[1].ToolCalls)
}
if len(msgs[1].ToolResults) != 1 || msgs[1].ToolResults[0].ToolUseID != "call_1" {
t.Fatalf("tool results = %+v", msgs[1].ToolResults)
}
tr := msgs[1].ToolResults[0]
if got := DecodeContent(tr.ContentRaw); got != "ok" {
t.Fatalf("tool result ContentRaw decoded = %q, want %q (raw=%s)", got, "ok", tr.ContentRaw)
}
ue := results[0].UsageEvents
if len(ue) != 1 {
t.Fatalf("UsageEvents len = %d, want 1", len(ue))
}
if ue[0].Model != "claude-opus-4" {
t.Fatalf("usage event model = %q, want %q", ue[0].Model, "claude-opus-4")
}
// input: 1000+1500=2500; output: 200+300=500
if ue[0].InputTokens != 2500 || ue[0].OutputTokens != 500 {
t.Fatalf("usage event tokens: in=%d (want 2500), out=%d (want 500)", ue[0].InputTokens, ue[0].OutputTokens)
}
if ue[0].DedupKey != "session:zed:10431c84-c47b-4e6c-b2df-f9f3b9ad025b" {
t.Fatalf("usage event dedup key = %q", ue[0].DedupKey)
}
}
func TestExtractZedDocMeta(t *testing.T) {
tests := []struct {
name string
doc map[string]any
wantModel string
wantTotalIn int
wantTotalOut int
wantPeakCtx int
wantHasUsage bool
}{
{
name: "empty doc",
doc: map[string]any{},
wantHasUsage: false,
},
{
name: "model only",
doc: map[string]any{
"model": map[string]any{"model": "gpt-4o", "provider": "openai"},
},
wantModel: "gpt-4o",
wantHasUsage: false,
},
{
name: "model and request usage",
doc: map[string]any{
"model": map[string]any{"model": "claude-opus-4", "provider": "anthropic"},
"request_token_usage": map[string]any{
"r1": map[string]any{"input_tokens": float64(1000), "output_tokens": float64(100)},
"r2": map[string]any{"input_tokens": float64(2000), "output_tokens": float64(200)},
},
},
wantModel: "claude-opus-4",
wantTotalIn: 3000,
wantTotalOut: 300,
wantPeakCtx: 2000,
wantHasUsage: true,
},
{
name: "empty request_token_usage",
doc: map[string]any{
"model": map[string]any{"model": "gpt-5.5", "provider": "newapi"},
"request_token_usage": map[string]any{},
},
wantModel: "gpt-5.5",
wantHasUsage: false,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := extractZedDocMeta(tc.doc)
if got.model != tc.wantModel {
t.Errorf("model = %q, want %q", got.model, tc.wantModel)
}
if got.totalInputTokens != tc.wantTotalIn {
t.Errorf("totalInputTokens = %d, want %d", got.totalInputTokens, tc.wantTotalIn)
}
if got.totalOutputTokens != tc.wantTotalOut {
t.Errorf("totalOutputTokens = %d, want %d", got.totalOutputTokens, tc.wantTotalOut)
}
if got.peakContextTokens != tc.wantPeakCtx {
t.Errorf("peakContextTokens = %d, want %d", got.peakContextTokens, tc.wantPeakCtx)
}
if got.hasTokenUsage != tc.wantHasUsage {
t.Errorf("hasTokenUsage = %v, want %v", got.hasTokenUsage, tc.wantHasUsage)
}
})
}
}
func TestParseZedSessions_ZstdAndFiltersChildren(t *testing.T) {
encoder, err := zstd.NewWriter(nil)
if err != nil {
t.Fatal(err)
}
compressed := encoder.EncodeAll([]byte(`{
"messages": [{"User": {"content": [{"Text": "Hello"}]}}]
}`), nil)
encoder.Close()
dbPath := createZedThreadsDB(t, []zedTestThread{
{
id: "parent",
summary: "Parent",
updatedAt: "2026-06-08T09:14:10Z",
dataType: "zstd",
data: compressed,
},
{
id: "child",
summary: "Child",
parentID: "parent",
updatedAt: "2026-06-08T09:14:11Z",
dataType: "json",
data: []byte(`{"messages":[{"User":{"content":[{"Text":"skip"}]}}]}`),
},
})
results, err := parseZedAll(dbPath, "local")
if err != nil {
t.Fatalf("ParseZedSessions: %v", err)
}
if len(results) != 1 || results[0].Session.ID != "zed:parent" {
t.Fatalf("results = %+v", results)
}
if results[0].Messages[0].Content != "Hello" {
t.Fatalf("message = %+v", results[0].Messages[0])
}
}
func TestParseZedSessions_SkipsUnsupportedDataType(t *testing.T) {
dbPath := createZedThreadsDB(t, []zedTestThread{{
id: "bad",
dataType: "brotli",
data: []byte("x"),
}})
results, err := parseZedAll(dbPath, "local")
if err != nil {
t.Fatalf("ParseZedSessions: %v", err)
}
if len(results) != 0 {
t.Fatalf("len(results) = %d, want 0", len(results))
}
}
func TestParseZedVirtualPath(t *testing.T) {
dbPath := filepath.Join("/tmp", "with#hash", "threads.db")
virtual := ZedSQLiteVirtualPath(dbPath, "sess-1")
gotDB, gotID, ok := parseZedVirtualPath(virtual)
if !ok || gotDB != dbPath || gotID != "sess-1" {
t.Fatalf("parseZedVirtualPath = (%q, %q, %v)", gotDB, gotID, ok)
}
if _, _, ok := parseZedVirtualPath("/tmp/not-db#sess-1"); ok {
t.Fatal("parseZedVirtualPath accepted non-threads DB")
}
}
type zedTestThread struct {
id string
summary string
updatedAt string
dataType string
data []byte
parentID string
folderPaths string
createdAt string
}
func createZedThreadsDB(t *testing.T, threads []zedTestThread) string {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "threads.db")
createZedThreadsDBAt(t, dbPath, threads)
return dbPath
}
func createZedThreadsDBAt(
t *testing.T,
dbPath string,
threads []zedTestThread,
) {
db, err := sql.Open("sqlite3", dbPath)
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE threads (
id TEXT PRIMARY KEY,
summary TEXT NOT NULL,
updated_at TEXT NOT NULL,
data_type TEXT NOT NULL,
data BLOB NOT NULL,
parent_id TEXT,
folder_paths TEXT,
folder_paths_order TEXT,
created_at TEXT
)`)
if err != nil {
t.Fatal(err)
}
for _, thread := range threads {
_, err = db.Exec(`INSERT INTO threads (
id, summary, updated_at, data_type, data,
parent_id, folder_paths, created_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
thread.id,
thread.summary,
thread.updatedAt,
thread.dataType,
thread.data,
nullString(thread.parentID),
thread.folderPaths,
thread.createdAt,
)
if err != nil {
t.Fatal(err)
}
}
}
func nullString(s string) any {
if s == "" {
return nil
}
return s
}