1- import { afterEach , describe , expect , test } from "bun:test"
1+ import { afterEach , describe , expect , mock , spyOn , test } from "bun:test"
22import { mkdir } from "node:fs/promises"
33import path from "node:path"
44import { Effect } from "effect"
@@ -13,6 +13,7 @@ import { Server } from "../../src/server/server"
1313import { resetDatabase } from "../fixture/db"
1414import { tmpdir } from "../fixture/fixture"
1515import { Instance } from "../../src/project/instance"
16+ import { InstancePaths } from "../../src/server/routes/instance/httpapi/groups/instance"
1617
1718void Log . init ( { print : false } )
1819
@@ -54,7 +55,41 @@ function localAdaptor(directory: string): WorkspaceAdaptor {
5455 }
5556}
5657
58+ function remoteAdaptor ( directory : string , url : string ) : WorkspaceAdaptor {
59+ return {
60+ name : "Remote Test" ,
61+ description : "Create a remote test workspace" ,
62+ configure ( info ) {
63+ return {
64+ ...info ,
65+ name : "remote-test" ,
66+ directory,
67+ }
68+ } ,
69+ async create ( ) {
70+ await mkdir ( directory , { recursive : true } )
71+ } ,
72+ async remove ( ) { } ,
73+ target ( ) {
74+ return {
75+ type : "remote" as const ,
76+ url,
77+ }
78+ } ,
79+ }
80+ }
81+
82+ function eventStreamResponse ( ) {
83+ return new Response ( new ReadableStream ( { start ( ) { } } ) , {
84+ status : 200 ,
85+ headers : {
86+ "content-type" : "text/event-stream" ,
87+ } ,
88+ } )
89+ }
90+
5791afterEach ( async ( ) => {
92+ mock . restore ( )
5893 Flag . OPENCODE_EXPERIMENTAL_WORKSPACES = originalWorkspaces
5994 Flag . OPENCODE_EXPERIMENTAL_HTTPAPI = originalHttpApi
6095 await Instance . disposeAll ( )
@@ -125,4 +160,81 @@ describe("workspace HttpApi", () => {
125160 expect ( listed . status ) . toBe ( 200 )
126161 expect ( await listed . json ( ) ) . toEqual ( [ ] )
127162 } )
163+
164+ test ( "routes local workspace requests through the workspace target directory" , async ( ) => {
165+ Flag . OPENCODE_EXPERIMENTAL_WORKSPACES = true
166+ await using tmp = await tmpdir ( { git : true } )
167+ const workspaceDir = path . join ( tmp . path , ".workspace-local" )
168+ const workspace = await Instance . provide ( {
169+ directory : tmp . path ,
170+ fn : async ( ) => {
171+ registerAdaptor ( Instance . project . id , "local-target" , localAdaptor ( workspaceDir ) )
172+ return Workspace . create ( {
173+ type : "local-target" ,
174+ branch : null ,
175+ extra : null ,
176+ projectID : Instance . project . id ,
177+ } )
178+ } ,
179+ } )
180+
181+ const url = new URL ( `http://localhost${ InstancePaths . path } ` )
182+ url . searchParams . set ( "workspace" , workspace . id )
183+
184+ try {
185+ const response = await request ( url . toString ( ) , tmp . path )
186+
187+ expect ( response . status ) . toBe ( 200 )
188+ expect ( await response . json ( ) ) . toMatchObject ( { directory : workspaceDir } )
189+ } finally {
190+ await Workspace . remove ( workspace . id )
191+ }
192+ } )
193+
194+ test ( "proxies remote workspace HTTP requests" , async ( ) => {
195+ Flag . OPENCODE_EXPERIMENTAL_WORKSPACES = true
196+ await using tmp = await tmpdir ( { git : true } )
197+ const proxied : string [ ] = [ ]
198+ const rawFetch = globalThis . fetch
199+ spyOn ( globalThis , "fetch" ) . mockImplementation (
200+ Object . assign (
201+ async ( input : URL | RequestInfo , init ?: BunFetchRequestInit | RequestInit ) => {
202+ const url = new URL ( typeof input === "string" || input instanceof URL ? input : input . url )
203+ if ( url . pathname === "/base/global/event" ) return eventStreamResponse ( )
204+ if ( url . pathname === "/base/sync/history" ) return Response . json ( [ ] )
205+ proxied . push ( url . toString ( ) )
206+ return Response . json ( { proxied : true , path : url . pathname , workspace : url . searchParams . get ( "workspace" ) } )
207+ } ,
208+ {
209+ preconnect : rawFetch . preconnect ?. bind ( rawFetch ) ,
210+ } ,
211+ ) as typeof globalThis . fetch ,
212+ )
213+
214+ const workspace = await Instance . provide ( {
215+ directory : tmp . path ,
216+ fn : async ( ) => {
217+ registerAdaptor ( Instance . project . id , "remote-target" , remoteAdaptor ( path . join ( tmp . path , ".remote" ) , "https://remote.test/base" ) )
218+ return Workspace . create ( {
219+ type : "remote-target" ,
220+ branch : null ,
221+ extra : null ,
222+ projectID : Instance . project . id ,
223+ } )
224+ } ,
225+ } )
226+
227+ const url = new URL ( `http://localhost${ InstancePaths . path } ` )
228+ url . searchParams . set ( "workspace" , workspace . id )
229+
230+ try {
231+ const response = await request ( url . toString ( ) , tmp . path )
232+
233+ expect ( response . status ) . toBe ( 200 )
234+ expect ( await response . json ( ) ) . toEqual ( { proxied : true , path : "/base/path" , workspace : null } )
235+ expect ( proxied ) . toEqual ( [ "https://remote.test/base/path" ] )
236+ } finally {
237+ await Workspace . remove ( workspace . id )
238+ }
239+ } )
128240} )
0 commit comments