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

486 lines
15 KiB
Go

package parser
import (
"bytes"
"context"
"database/sql"
"os"
"path/filepath"
"strings"
"testing"
"time"
_ "github.com/mattn/go-sqlite3"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestWindsurfProviderDiscoversAndParsesWorkspaceSQLiteChat(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"windsurf-session-1",
"How do I add support?",
"Use the existing parser.",
))
provider := newTestWindsurfProvider(root)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
source := sources[0]
assert.Equal(t, AgentWindsurf, source.Provider)
assert.Equal(t, "demo-workspace", source.ProjectHint)
assert.Equal(t, dbPath+"#windsurf-session-1", source.DisplayPath)
fp, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
require.NotZero(t, fp.Size)
require.NotZero(t, fp.MTimeNS)
require.NotEmpty(t, fp.Hash)
out, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
Fingerprint: fp,
Machine: "machine-a",
})
require.NoError(t, err)
require.True(t, out.ResultSetComplete)
require.Len(t, out.Results, 1)
result := out.Results[0].Result
assert.Equal(t, "windsurf:windsurf-session-1", result.Session.ID)
assert.Equal(t, AgentWindsurf, result.Session.Agent)
assert.Equal(t, "demo-workspace", result.Session.Project)
assert.Equal(t, "machine-a", result.Session.Machine)
assert.Equal(t, source.DisplayPath, result.Session.File.Path)
require.Len(t, result.Messages, 2)
assert.Equal(t, RoleUser, result.Messages[0].Role)
assert.Equal(t, "How do I add support?", result.Messages[0].Content)
assert.Equal(t, RoleAssistant, result.Messages[1].Role)
assert.Equal(t, "Use the existing parser.", result.Messages[1].Content)
}
func TestWindsurfProviderFingerprintHashIsContentBased(t *testing.T) {
payload := windsurfVSCodeSessionJSON(
"windsurf-session-hash",
"Hash this",
"Same content.",
)
rootA, _ := windsurfProviderFixture(t, payload)
rootB, _ := windsurfProviderFixture(t, payload)
providerA := newTestWindsurfProvider(rootA)
providerB := newTestWindsurfProvider(rootB)
sourcesA, err := providerA.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sourcesA, 1)
sourcesB, err := providerB.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sourcesB, 1)
fpA, err := providerA.Fingerprint(context.Background(), sourcesA[0])
require.NoError(t, err)
fpB, err := providerB.Fingerprint(context.Background(), sourcesB[0])
require.NoError(t, err)
require.NotEmpty(t, fpA.Hash)
assert.Equal(t, fpA.Hash, fpB.Hash)
}
func TestWindsurfProviderFingerprintIgnoresSHM(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"windsurf-session-shm",
"Ignore SHM",
"Use chat data only.",
))
provider := newTestWindsurfProvider(root)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
require.NoError(t, os.WriteFile(dbPath+"-shm", []byte("first"), 0o644))
firstTime := mustParseTestTime(t, "2026-06-28T12:00:00Z")
require.NoError(t, os.Chtimes(dbPath+"-shm", firstTime, firstTime))
before, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
require.NoError(t, os.WriteFile(dbPath+"-shm", []byte("second"), 0o644))
secondTime := mustParseTestTime(t, "2026-06-28T13:00:00Z")
require.NoError(t, os.Chtimes(dbPath+"-shm", secondTime, secondTime))
after, err := provider.Fingerprint(context.Background(), sources[0])
require.NoError(t, err)
assert.Equal(t, before.Hash, after.Hash)
assert.Equal(t, before.Size, after.Size)
assert.Equal(t, before.MTimeNS, after.MTimeNS)
}
func TestWindsurfProviderParsesTabContainerChatData(t *testing.T) {
root, _ := windsurfProviderFixture(t, `{
"tabs": [{
"tabId": "tab-session",
"chatTitle": "Tab title",
"bubbles": [
{"type": "user", "text": "Question from tab"},
{"type": "assistant", "text": "Answer from tab"}
]
}]
}`)
provider := newTestWindsurfProvider(root)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
out, err := provider.Parse(context.Background(), ParseRequest{
Source: sources[0],
})
require.NoError(t, err)
require.Len(t, out.Results, 1)
result := out.Results[0].Result
assert.Equal(t, "windsurf:tab-session", result.Session.ID)
require.Len(t, result.Messages, 2)
assert.Equal(t, "Question from tab", result.Messages[0].Content)
assert.Equal(t, "Answer from tab", result.Messages[1].Content)
}
func TestWindsurfProviderMalformedChatDataReturnsError(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, `{"tabs":`)
provider := newTestWindsurfProvider(root)
_, err := provider.Parse(context.Background(), ParseRequest{
Source: SourceRef{
Provider: AgentWindsurf,
DisplayPath: dbPath + "#corrupt-session",
FingerprintKey: dbPath + "#corrupt-session",
},
})
require.Error(t, err)
assert.ErrorContains(t, err, "parse windsurf chatdata")
}
func TestWindsurfProviderParsesNumericTabBubbleTypes(t *testing.T) {
root, _ := windsurfProviderFixture(t, `{
"tabs": [{
"tabId": "numeric-tab-session",
"chatTitle": "Numeric tab",
"bubbles": [
{"type": 1, "text": "Numeric question"},
{"type": 2, "text": "Numeric answer"}
]
}]
}`)
provider := newTestWindsurfProvider(root)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
out, err := provider.Parse(context.Background(), ParseRequest{
Source: sources[0],
})
require.NoError(t, err)
require.Len(t, out.Results, 1)
messages := out.Results[0].Result.Messages
require.Len(t, messages, 2)
assert.Equal(t, RoleUser, messages[0].Role)
assert.Equal(t, "Numeric question", messages[0].Content)
assert.Equal(t, RoleAssistant, messages[1].Role)
assert.Equal(t, "Numeric answer", messages[1].Content)
}
func TestWindsurfProviderFallbackSessionIDUsesWorkspaceIdentity(t *testing.T) {
root := filepath.Join(t.TempDir(), "Windsurf", "User")
dbA := filepath.Join(root, "workspaceStorage", "workspace-a", "state.vscdb")
dbB := filepath.Join(root, "workspaceStorage", "workspace-b", "state.vscdb")
payload := `{
"version": 1,
"requests": [{
"requestId": "request-1",
"message": {"text": "Missing session"},
"response": [{"value": "Fallback response"}],
"timestamp": 1710000000000
}]
}`
writeSourceFile(t, filepath.Join(filepath.Dir(dbA), "workspace.json"), `{"folder":"file:///work/a"}`)
writeSourceFile(t, filepath.Join(filepath.Dir(dbB), "workspace.json"), `{"folder":"file:///work/b"}`)
writeWindsurfStateDB(t, dbA, payload)
writeWindsurfStateDB(t, dbB, payload)
provider := newTestWindsurfProvider(root)
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 2)
assert.ElementsMatch(t, []string{
dbA + "#workspace-workspace-a",
dbB + "#workspace-workspace-b",
}, []string{sources[0].DisplayPath, sources[1].DisplayPath})
var ids []string
for _, source := range sources {
out, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
})
require.NoError(t, err)
require.Len(t, out.Results, 1)
ids = append(ids, out.Results[0].Result.Session.ID)
}
assert.ElementsMatch(t, []string{
"windsurf:workspace-workspace-a",
"windsurf:workspace-workspace-b",
}, ids)
}
func TestWindsurfProviderFindSourceAndChangedPath(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"windsurf-session-lookup",
"Find me",
"Found.",
))
provider := newTestWindsurfProvider(root)
found, ok, err := provider.FindSource(context.Background(), FindSourceRequest{
RawSessionID: "windsurf-session-lookup",
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, dbPath+"#windsurf-session-lookup", found.DisplayPath)
found, ok, err = provider.FindSource(context.Background(), FindSourceRequest{
FullSessionID: "windsurf:windsurf-session-lookup",
StoredFilePath: dbPath + "#windsurf-session-lookup",
RequireFreshSource: true,
})
require.NoError(t, err)
require.True(t, ok)
assert.Equal(t, dbPath+"#windsurf-session-lookup", found.DisplayPath)
changed, err := provider.SourcesForChangedPath(context.Background(), ChangedPathRequest{
Path: dbPath + "-wal",
EventKind: "write",
WatchRoot: filepath.Join(
root,
"workspaceStorage",
),
})
require.NoError(t, err)
require.Len(t, changed, 1)
assert.Equal(t, dbPath+"#windsurf-session-lookup", changed[0].DisplayPath)
manifestChanged, err := provider.SourcesForChangedPath(context.Background(), ChangedPathRequest{
Path: filepath.Join(
filepath.Dir(dbPath),
"workspace.json",
),
EventKind: "write",
WatchRoot: filepath.Join(
root,
"workspaceStorage",
),
})
require.NoError(t, err)
require.Len(t, manifestChanged, 1)
assert.Equal(t, dbPath+"#windsurf-session-lookup", manifestChanged[0].DisplayPath)
}
func TestWindsurfProviderDeletedDBChangedPathPreservesStoredArchive(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"deleted-session",
"Question before delete",
"Answer before delete.",
))
provider := newTestWindsurfProvider(root)
source := SourceRef{
Provider: AgentWindsurf,
Key: dbPath + "#deleted-session",
DisplayPath: dbPath + "#deleted-session",
FingerprintKey: dbPath + "#deleted-session",
}
require.NoError(t, os.Remove(dbPath))
changed, err := provider.SourcesForChangedPath(context.Background(), ChangedPathRequest{
Path: dbPath,
EventKind: "remove",
WatchRoot: filepath.Join(root, "workspaceStorage"),
StoredSourcePaths: []string{source.DisplayPath},
})
require.NoError(t, err)
assert.Empty(t, changed)
fp, err := provider.Fingerprint(context.Background(), source)
require.NoError(t, err)
assert.Equal(t, source.FingerprintKey, fp.Key)
assert.Empty(t, fp.Hash)
out, err := provider.Parse(context.Background(), ParseRequest{
Source: source,
})
require.NoError(t, err)
assert.True(t, out.ResultSetComplete)
assert.False(t, out.ForceReplace)
assert.Equal(t, SkipNoSession, out.SkipReason)
}
func TestSplitWindsurfVirtualPathRequiresStateDB(t *testing.T) {
dbPath, sessionID, ok := SplitWindsurfVirtualPath(
filepath.Join("profile", "workspaceStorage", "hash", "state.vscdb") + "#session",
)
require.True(t, ok)
assert.Equal(t, "session", sessionID)
assert.True(t, strings.HasSuffix(filepath.ToSlash(dbPath), "/state.vscdb"))
_, _, ok = SplitWindsurfVirtualPath(
filepath.Join("profile", "notes#draft.json") + "#session",
)
assert.False(t, ok)
}
func TestWindsurfProviderWatchPlan(t *testing.T) {
provider := newTestWindsurfProvider(filepath.Join(t.TempDir(), "Windsurf", "User"))
plan, err := provider.WatchPlan(context.Background())
require.NoError(t, err)
require.Len(t, plan.Roots, 1)
root := plan.Roots[0]
assert.True(t, root.Recursive)
assert.True(t, strings.HasSuffix(filepath.ToSlash(root.Path), "/workspaceStorage"))
assert.Contains(t, root.IncludeGlobs, "state.vscdb")
assert.Contains(t, root.IncludeGlobs, "state.vscdb-wal")
assert.NotContains(t, root.IncludeGlobs, "state.vscdb-*")
assert.NotContains(t, root.IncludeGlobs, "state.vscdb-shm")
assert.Contains(t, root.IncludeGlobs, "workspace.json")
}
func TestWindsurfProviderAcceptsWorkspaceStorageRoot(t *testing.T) {
root, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"workspace-root-session",
"Root question",
"Root answer.",
))
provider := newTestWindsurfProvider(filepath.Join(root, "workspaceStorage"))
sources, err := provider.Discover(context.Background())
require.NoError(t, err)
require.Len(t, sources, 1)
assert.Equal(t, dbPath+"#workspace-root-session", sources[0].DisplayPath)
plan, err := provider.WatchPlan(context.Background())
require.NoError(t, err)
require.Len(t, plan.Roots, 1)
assert.Equal(t, filepath.Join(root, "workspaceStorage"), plan.Roots[0].Path)
}
func TestWriteWindsurfSessionJSONScopesVirtualSource(t *testing.T) {
_, dbPath := windsurfProviderFixture(t, `{
"tabs": [
{"tabId": "export-a", "bubbles": [{"type": "user", "text": "A only"}]},
{"tabId": "export-b", "bubbles": [{"type": "user", "text": "B hidden"}]}
]
}`)
var buf bytes.Buffer
require.NoError(t, WriteWindsurfSessionJSON(&buf, dbPath, "export-a"))
assert.Contains(t, buf.String(), "export-a")
assert.Contains(t, buf.String(), "A only")
assert.NotContains(t, buf.String(), "export-b")
assert.NotContains(t, buf.String(), "B hidden")
}
func TestWriteSanitizedWindsurfStateDBCopiesOnlyChatKeys(t *testing.T) {
_, dbPath := windsurfProviderFixture(t, windsurfVSCodeSessionJSON(
"sanitized-export",
"Export chat",
"Do not export secrets.",
))
insertWindsurfStateRow(t, dbPath, "extension.secret", "TOP-SECRET")
outPath := filepath.Join(t.TempDir(), "state.vscdb")
require.NoError(t, WriteSanitizedWindsurfStateDB(outPath, dbPath))
conn, err := sql.Open("sqlite3", outPath)
require.NoError(t, err)
defer conn.Close()
rows, err := conn.Query(`SELECT key, value FROM ItemTable ORDER BY key`)
require.NoError(t, err)
defer rows.Close()
got := make(map[string]string)
for rows.Next() {
var key, value string
require.NoError(t, rows.Scan(&key, &value))
got[key] = value
}
require.NoError(t, rows.Err())
assert.Contains(t, got, "workbench.panel.aichat.view.aichat.chatdata")
assert.Contains(t, got["workbench.panel.aichat.view.aichat.chatdata"], "Export chat")
assert.NotContains(t, got, "extension.secret")
}
func newTestWindsurfProvider(root string) Provider {
provider, ok := NewProvider(AgentWindsurf, ProviderConfig{
Roots: []string{root},
})
if !ok {
panic("missing Windsurf provider")
}
return provider
}
func windsurfProviderFixture(t *testing.T, payload string) (string, string) {
t.Helper()
root := filepath.Join(t.TempDir(), "Windsurf", "User")
workspaceDir := filepath.Join(root, "workspaceStorage", "workspace-hash")
writeSourceFile(t, filepath.Join(workspaceDir, "workspace.json"), `{"folder":"file:///work/demo-workspace"}`)
dbPath := filepath.Join(workspaceDir, "state.vscdb")
writeWindsurfStateDB(t, dbPath, payload)
return root, dbPath
}
func writeWindsurfStateDB(t *testing.T, dbPath, payload string) {
t.Helper()
conn, err := sql.Open("sqlite3", dbPath)
require.NoError(t, err)
defer conn.Close()
_, err = conn.Exec(`CREATE TABLE ItemTable (key TEXT PRIMARY KEY, value TEXT)`)
require.NoError(t, err)
_, err = conn.Exec(
`INSERT INTO ItemTable (key, value) VALUES (?, ?)`,
"workbench.panel.aichat.view.aichat.chatdata",
payload,
)
require.NoError(t, err)
}
func insertWindsurfStateRow(t *testing.T, dbPath, key, value string) {
t.Helper()
conn, err := sql.Open("sqlite3", dbPath)
require.NoError(t, err)
defer conn.Close()
_, err = conn.Exec(
`INSERT INTO ItemTable (key, value) VALUES (?, ?)`,
key,
value,
)
require.NoError(t, err)
}
func mustParseTestTime(t *testing.T, value string) time.Time {
t.Helper()
parsed, err := time.Parse(time.RFC3339, value)
require.NoError(t, err)
return parsed
}
func windsurfVSCodeSessionJSON(sessionID, user, assistant string) string {
return `{
"version": 1,
"sessionId": "` + sessionID + `",
"creationDate": 1710000000000,
"lastMessageDate": 1710000001000,
"requests": [{
"requestId": "request-1",
"message": {"text": "` + user + `"},
"response": [{"value": "` + assistant + `"}],
"timestamp": 1710000000000
}]
}`
}