@@ -120,6 +120,171 @@ func TestDetect_SkipsGlobal(t *testing.T) {
120120 }
121121}
122122
123+ func TestFindProjectID_WithSandboxPaths (t * testing.T ) {
124+ tmpDir := t .TempDir ()
125+ projectDir := filepath .Join (tmpDir , "project" )
126+ if err := os .MkdirAll (projectDir , 0755 ); err != nil {
127+ t .Fatalf ("failed to create project dir: %v" , err )
128+ }
129+
130+ // Create a project with sandboxes
131+ worktreePath := filepath .Join (tmpDir , "repos" , "core.zdaj.app" )
132+ sandboxPath := filepath .Join (worktreePath , "main" )
133+
134+ projectJSON := fmt .Sprintf (`{
135+ "id": "test_project_sandbox",
136+ "worktree": "%s",
137+ "vcs": "git",
138+ "sandboxes": ["%s"],
139+ "time": { "created": 1767000000000, "updated": 1767100000000 }
140+ }` , worktreePath , sandboxPath )
141+
142+ if err := os .WriteFile (filepath .Join (projectDir , "test_sandbox.json" ), []byte (projectJSON ), 0644 ); err != nil {
143+ t .Fatalf ("failed to write project file: %v" , err )
144+ }
145+
146+ a := & Adapter {
147+ storageDir : tmpDir ,
148+ projectIndex : make (map [string ]* Project ),
149+ sessionIndex : make (map [string ]string ),
150+ metaCache : make (map [string ]sessionMetaCacheEntry ),
151+ }
152+
153+ // Test 1: Find project by worktree path
154+ projectID , err := a .findProjectID (worktreePath )
155+ if err != nil {
156+ t .Fatalf ("findProjectID error: %v" , err )
157+ }
158+ if projectID != "test_project_sandbox" {
159+ t .Errorf ("expected project ID %q, got %q" , "test_project_sandbox" , projectID )
160+ }
161+
162+ // Test 2: Find project by sandbox path
163+ projectID , err = a .findProjectID (sandboxPath )
164+ if err != nil {
165+ t .Fatalf ("findProjectID error for sandbox: %v" , err )
166+ }
167+ if projectID != "test_project_sandbox" {
168+ t .Errorf ("expected project ID %q for sandbox path, got %q" , "test_project_sandbox" , projectID )
169+ }
170+
171+ // Test 3: Path not in sandboxes should not be found
172+ unrelatedPath := filepath .Join (tmpDir , "repos" , "other-repo" )
173+ projectID , err = a .findProjectID (unrelatedPath )
174+ if err != nil {
175+ t .Fatalf ("findProjectID error: %v" , err )
176+ }
177+ if projectID != "" {
178+ t .Errorf ("expected empty project ID for unrelated path, got %q" , projectID )
179+ }
180+ }
181+
182+
183+ func TestFindProjectID_SubdirectoryMatch (t * testing.T ) {
184+ tmpDir := t .TempDir ()
185+ projectDir := filepath .Join (tmpDir , "project" )
186+ if err := os .MkdirAll (projectDir , 0755 ); err != nil {
187+ t .Fatalf ("failed to create project dir: %v" , err )
188+ }
189+
190+ // Simulate bare-repo layout: OpenCode registers /repo as worktree,
191+ // but sidecar's ProjectRoot resolves to /repo/.bare (the main worktree).
192+ worktreePath := filepath .Join (tmpDir , "repos" , "core.zdaj.app" )
193+ barePath := filepath .Join (worktreePath , ".bare" )
194+ sandboxPath := filepath .Join (worktreePath , "main" )
195+
196+ projectJSON := fmt .Sprintf (`{
197+ "id": "test_project_subdir",
198+ "worktree": "%s",
199+ "vcs": "git",
200+ "sandboxes": ["%s"],
201+ "time": { "created": 1767000000000, "updated": 1767100000000 }
202+ }` , worktreePath , sandboxPath )
203+
204+ if err := os .WriteFile (filepath .Join (projectDir , "test_subdir.json" ), []byte (projectJSON ), 0644 ); err != nil {
205+ t .Fatalf ("failed to write project file: %v" , err )
206+ }
207+
208+ a := & Adapter {
209+ storageDir : tmpDir ,
210+ projectIndex : make (map [string ]* Project ),
211+ sessionIndex : make (map [string ]string ),
212+ metaCache : make (map [string ]sessionMetaCacheEntry ),
213+ }
214+
215+ // Test 1: .bare subdirectory should match via subdirectory fallback
216+ projectID , err := a .findProjectID (barePath )
217+ if err != nil {
218+ t .Fatalf ("findProjectID error for .bare path: %v" , err )
219+ }
220+ if projectID != "test_project_subdir" {
221+ t .Errorf ("expected project ID %q for .bare subdirectory, got %q" , "test_project_subdir" , projectID )
222+ }
223+
224+ // Test 2: Nested subdirectory should also match
225+ nestedPath := filepath .Join (worktreePath , ".bare" , "worktrees" , "main" )
226+ projectID , err = a .findProjectID (nestedPath )
227+ if err != nil {
228+ t .Fatalf ("findProjectID error for nested path: %v" , err )
229+ }
230+ if projectID != "test_project_subdir" {
231+ t .Errorf ("expected project ID %q for nested subdirectory, got %q" , "test_project_subdir" , projectID )
232+ }
233+
234+ // Test 3: Sibling directory should NOT match (prefix attack prevention)
235+ siblingPath := filepath .Join (tmpDir , "repos" , "core.zdaj.app-other" )
236+ projectID , err = a .findProjectID (siblingPath )
237+ if err != nil {
238+ t .Fatalf ("findProjectID error for sibling path: %v" , err )
239+ }
240+ if projectID != "" {
241+ t .Errorf ("expected empty project ID for sibling path, got %q" , projectID )
242+ }
243+ }
244+ func TestFindProjectID_SandboxNotDuplicated (t * testing.T ) {
245+ tmpDir := t .TempDir ()
246+ projectDir := filepath .Join (tmpDir , "project" )
247+ if err := os .MkdirAll (projectDir , 0755 ); err != nil {
248+ t .Fatalf ("failed to create project dir: %v" , err )
249+ }
250+
251+ // Create a project where sandbox path equals worktree path (should not duplicate)
252+ worktreePath := filepath .Join (tmpDir , "repos" , "myrepo" )
253+
254+ projectJSON := fmt .Sprintf (`{
255+ "id": "test_project_no_dup",
256+ "worktree": "%s",
257+ "vcs": "git",
258+ "sandboxes": ["%s"],
259+ "time": { "created": 1767000000000, "updated": 1767100000000 }
260+ }` , worktreePath , worktreePath )
261+
262+ if err := os .WriteFile (filepath .Join (projectDir , "test_no_dup.json" ), []byte (projectJSON ), 0644 ); err != nil {
263+ t .Fatalf ("failed to write project file: %v" , err )
264+ }
265+
266+ a := & Adapter {
267+ storageDir : tmpDir ,
268+ projectIndex : make (map [string ]* Project ),
269+ sessionIndex : make (map [string ]string ),
270+ metaCache : make (map [string ]sessionMetaCacheEntry ),
271+ }
272+
273+ // Should find project by worktree path
274+ projectID , err := a .findProjectID (worktreePath )
275+ if err != nil {
276+ t .Fatalf ("findProjectID error: %v" , err )
277+ }
278+ if projectID != "test_project_no_dup" {
279+ t .Errorf ("expected project ID %q, got %q" , "test_project_no_dup" , projectID )
280+ }
281+
282+ // Verify projectIndex has only one entry for this path (no duplication)
283+ if len (a .projectIndex ) != 1 {
284+ t .Errorf ("expected 1 entry in projectIndex, got %d" , len (a .projectIndex ))
285+ }
286+ }
287+
123288func TestSessions_WithTestdata (t * testing.T ) {
124289 a := newTestAdapter (t )
125290 testdataDir := getTestdataDir (t )
0 commit comments