2026-04-03 00:56:56 +00:00
import { NodeFileSystem } from "@effect/platform-node"
import { beforeEach , describe , expect } from "bun:test"
import { Effect , Exit , Layer , Option } from "effect"
import { HttpClient , HttpClientRequest , HttpClientResponse } from "effect/unstable/http"
2026-04-17 01:55:50 +00:00
import { AccessToken , AccountID , OrgID , RefreshToken } from "../../src/account/schema"
import { Account } from "../../src/account/account"
2026-04-03 00:56:56 +00:00
import { AccountRepo } from "../../src/account/repo"
2026-04-25 18:30:16 +00:00
import { CrossSpawnSpawner } from "@opencode-ai/core/cross-spawn-spawner"
2026-04-03 00:56:56 +00:00
import { Bus } from "../../src/bus"
2026-04-27 18:33:33 +00:00
import { Config } from "@/config/config"
import { Provider } from "@/provider/provider"
import { Session } from "@/session/session"
2026-04-03 00:56:56 +00:00
import type { SessionID } from "../../src/session/schema"
2026-04-27 18:33:33 +00:00
import { ShareNext } from "@/share/share-next"
2026-04-03 00:56:56 +00:00
import { SessionShareTable } from "../../src/share/share.sql"
2026-04-27 18:33:33 +00:00
import { Database } from "@/storage/db"
import { eq } from "drizzle-orm"
2026-04-03 00:56:56 +00:00
import { provideTmpdirInstance } from "../fixture/fixture"
import { resetDatabase } from "../fixture/db"
import { testEffect } from "../lib/effect"
2026-03-10 16:53:47 +00:00
2026-04-03 00:56:56 +00:00
const env = Layer . mergeAll (
Session . defaultLayer ,
AccountRepo . layer ,
NodeFileSystem . layer ,
CrossSpawnSpawner . defaultLayer ,
)
const it = testEffect ( env )
const json = ( req : Parameters < typeof HttpClientResponse.fromWeb > [ 0 ] , body : unknown , status = 200 ) = >
HttpClientResponse . fromWeb (
req ,
new Response ( JSON . stringify ( body ) , {
status ,
headers : { "content-type" : "application/json" } ,
} ) ,
)
const none = HttpClient . make ( ( ) = > Effect . die ( "unexpected http call" ) )
function live ( client : HttpClient.HttpClient ) {
const http = Layer . succeed ( HttpClient . HttpClient , client )
return ShareNext . layer . pipe (
Layer . provide ( Bus . layer ) ,
Layer . provide ( Account . layer . pipe ( Layer . provide ( AccountRepo . layer ) , Layer . provide ( http ) ) ) ,
Layer . provide ( Config . defaultLayer ) ,
Layer . provide ( http ) ,
Layer . provide ( Provider . defaultLayer ) ,
Layer . provide ( Session . defaultLayer ) ,
)
}
function wired ( client : HttpClient.HttpClient ) {
const http = Layer . succeed ( HttpClient . HttpClient , client )
return Layer . mergeAll (
Bus . layer ,
ShareNext . layer ,
2026-04-11 01:21:02 +00:00
Session . defaultLayer ,
2026-04-03 00:56:56 +00:00
AccountRepo . layer ,
NodeFileSystem . layer ,
CrossSpawnSpawner . defaultLayer ,
) . pipe (
Layer . provide ( Bus . layer ) ,
Layer . provide ( Account . layer . pipe ( Layer . provide ( AccountRepo . layer ) , Layer . provide ( http ) ) ) ,
Layer . provide ( Config . defaultLayer ) ,
Layer . provide ( http ) ,
Layer . provide ( Provider . defaultLayer ) ,
)
}
const share = ( id : SessionID ) = >
Database . use ( ( db ) = > db . select ( ) . from ( SessionShareTable ) . where ( eq ( SessionShareTable . session_id , id ) ) . get ( ) )
2026-03-10 16:53:47 +00:00
2026-04-03 00:56:56 +00:00
const seed = ( url : string , org? : string ) = >
2026-04-17 01:43:57 +00:00
AccountRepo . Service . use ( ( repo ) = >
2026-04-03 00:56:56 +00:00
repo . persistAccount ( {
id : AccountID.make ( "account-1" ) ,
email : "user@example.com" ,
url ,
accessToken : AccessToken.make ( "st_test_token" ) ,
refreshToken : RefreshToken.make ( "rt_test_token" ) ,
expiry : Date.now ( ) + 10 * 60 _000 ,
orgID : org ? Option . some ( OrgID . make ( org ) ) : Option . none ( ) ,
} ) ,
)
beforeEach ( async ( ) = > {
await resetDatabase ( )
2026-03-10 16:53:47 +00:00
} )
2026-04-03 00:56:56 +00:00
describe ( "ShareNext" , ( ) = > {
it . live ( "request uses legacy share API without active org account" , ( ) = >
provideTmpdirInstance (
( ) = >
ShareNext . Service . use ( ( svc ) = >
Effect . gen ( function * ( ) {
const req = yield * svc . request ( )
expect ( req . api . create ) . toBe ( "/api/share" )
expect ( req . api . sync ( "shr_123" ) ) . toBe ( "/api/share/shr_123/sync" )
expect ( req . api . remove ( "shr_123" ) ) . toBe ( "/api/share/shr_123" )
expect ( req . api . data ( "shr_123" ) ) . toBe ( "/api/share/shr_123/data" )
expect ( req . baseUrl ) . toBe ( "https://legacy-share.example.com" )
expect ( req . headers ) . toEqual ( { } )
} ) ,
) . pipe ( Effect . provide ( live ( none ) ) ) ,
{ config : { enterprise : { url : "https://legacy-share.example.com" } } } ,
) ,
)
it . live ( "request uses default URL when no enterprise config" , ( ) = >
provideTmpdirInstance ( ( ) = >
ShareNext . Service . use ( ( svc ) = >
Effect . gen ( function * ( ) {
const req = yield * svc . request ( )
expect ( req . baseUrl ) . toBe ( "https://opncd.ai" )
expect ( req . api . create ) . toBe ( "/api/share" )
expect ( req . headers ) . toEqual ( { } )
} ) ,
) . pipe ( Effect . provide ( live ( none ) ) ) ,
) ,
)
it . live ( "request uses org share API with auth headers when account is active" , ( ) = >
provideTmpdirInstance ( ( ) = >
Effect . gen ( function * ( ) {
yield * seed ( "https://control.example.com" , "org-1" )
const req = yield * ShareNext . Service . use ( ( svc ) = > svc . request ( ) ) . pipe ( Effect . provide ( live ( none ) ) )
expect ( req . api . create ) . toBe ( "/api/shares" )
expect ( req . api . sync ( "shr_123" ) ) . toBe ( "/api/shares/shr_123/sync" )
expect ( req . api . remove ( "shr_123" ) ) . toBe ( "/api/shares/shr_123" )
expect ( req . api . data ( "shr_123" ) ) . toBe ( "/api/shares/shr_123/data" )
expect ( req . baseUrl ) . toBe ( "https://control.example.com" )
expect ( req . headers ) . toEqual ( {
authorization : "Bearer st_test_token" ,
"x-org-id" : "org-1" ,
} )
} ) ,
) ,
)
it . live ( "create posts share, persists it, and returns the result" , ( ) = >
provideTmpdirInstance (
( ) = >
Effect . gen ( function * ( ) {
const session = yield * Session . Service . use ( ( svc ) = > svc . create ( { title : "test" } ) )
const seen : HttpClientRequest.HttpClientRequest [ ] = [ ]
const client = HttpClient . make ( ( req ) = > {
seen . push ( req )
if ( req . url . endsWith ( "/api/share" ) ) {
return Effect . succeed (
json ( req , {
id : "shr_abc" ,
url : "https://legacy-share.example.com/share/abc" ,
secret : "sec_123" ,
} ) ,
)
}
return Effect . succeed ( json ( req , { ok : true } ) )
} )
const result = yield * ShareNext . Service . use ( ( svc ) = > svc . create ( session . id ) ) . pipe (
Effect . provide ( live ( client ) ) ,
)
expect ( result . id ) . toBe ( "shr_abc" )
expect ( result . url ) . toBe ( "https://legacy-share.example.com/share/abc" )
expect ( result . secret ) . toBe ( "sec_123" )
const row = share ( session . id )
expect ( row ? . id ) . toBe ( "shr_abc" )
expect ( row ? . url ) . toBe ( "https://legacy-share.example.com/share/abc" )
expect ( row ? . secret ) . toBe ( "sec_123" )
expect ( seen ) . toHaveLength ( 1 )
expect ( seen [ 0 ] . method ) . toBe ( "POST" )
expect ( seen [ 0 ] . url ) . toBe ( "https://legacy-share.example.com/api/share" )
} ) ,
{ config : { enterprise : { url : "https://legacy-share.example.com" } } } ,
) ,
)
it . live ( "remove deletes the persisted share and calls the delete endpoint" , ( ) = >
provideTmpdirInstance (
( ) = >
Effect . gen ( function * ( ) {
const session = yield * Session . Service . use ( ( svc ) = > svc . create ( { title : "test" } ) )
const seen : HttpClientRequest.HttpClientRequest [ ] = [ ]
const client = HttpClient . make ( ( req ) = > {
seen . push ( req )
if ( req . method === "POST" ) {
return Effect . succeed (
json ( req , {
id : "shr_abc" ,
url : "https://legacy-share.example.com/share/abc" ,
secret : "sec_123" ,
} ) ,
)
}
return Effect . succeed ( HttpClientResponse . fromWeb ( req , new Response ( null , { status : 200 } ) ) )
} )
yield * Effect . gen ( function * ( ) {
yield * ShareNext . Service . use ( ( svc ) = > svc . create ( session . id ) )
yield * ShareNext . Service . use ( ( svc ) = > svc . remove ( session . id ) )
} ) . pipe ( Effect . provide ( live ( client ) ) )
expect ( share ( session . id ) ) . toBeUndefined ( )
expect ( seen . map ( ( req ) = > [ req . method , req . url ] ) ) . toEqual ( [
[ "POST" , "https://legacy-share.example.com/api/share" ] ,
[ "DELETE" , "https://legacy-share.example.com/api/share/shr_abc" ] ,
] )
} ) ,
{ config : { enterprise : { url : "https://legacy-share.example.com" } } } ,
) ,
)
it . live ( "create fails on a non-ok response and does not persist a share" , ( ) = >
provideTmpdirInstance ( ( ) = >
Effect . gen ( function * ( ) {
const session = yield * Session . Service . use ( ( svc ) = > svc . create ( { title : "test" } ) )
const client = HttpClient . make ( ( req ) = > Effect . succeed ( json ( req , { error : "bad" } , 500 ) ) )
const exit = yield * ShareNext . Service . use ( ( svc ) = > Effect . exit ( svc . create ( session . id ) ) ) . pipe (
Effect . provide ( live ( client ) ) ,
)
expect ( Exit . isFailure ( exit ) ) . toBe ( true )
expect ( share ( session . id ) ) . toBeUndefined ( )
} ) ,
) ,
)
it . live ( "ShareNext coalesces rapid diff events into one delayed sync with latest data" , ( ) = >
provideTmpdirInstance (
( ) = > {
const seen : Array < { url : string ; body : string } > = [ ]
const client = HttpClient . make ( ( req ) = > {
if ( req . url . endsWith ( "/sync" ) && req . body . _tag === "Uint8Array" ) {
seen . push ( { url : req.url , body : new TextDecoder ( ) . decode ( req . body . body ) } )
}
return Effect . succeed ( json ( req , { ok : true } ) )
} )
return Effect . gen ( function * ( ) {
const bus = yield * Bus . Service
const share = yield * ShareNext . Service
const session = yield * Session . Service
const info = yield * session . create ( { title : "first" } )
yield * share . init ( )
yield * Effect . sleep ( 50 )
yield * Effect . sync ( ( ) = >
Database . use ( ( db ) = >
db
. insert ( SessionShareTable )
. values ( {
session_id : info.id ,
id : "shr_abc" ,
url : "https://legacy-share.example.com/share/abc" ,
secret : "sec_123" ,
} )
. run ( ) ,
) ,
)
yield * bus . publish ( Session . Event . Diff , {
sessionID : info.id ,
diff : [
{
file : "a.ts" ,
2026-04-07 23:48:23 +00:00
patch :
"Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,1 +1,1 @@\n-one\n\\ No newline at end of file\n+two\n\\ No newline at end of file\n" ,
2026-04-03 00:56:56 +00:00
additions : 1 ,
deletions : 1 ,
status : "modified" ,
} ,
] ,
} )
yield * bus . publish ( Session . Event . Diff , {
sessionID : info.id ,
diff : [
{
file : "b.ts" ,
2026-04-07 23:48:23 +00:00
patch :
"Index: b.ts\n===================================================================\n--- b.ts\t\n+++ b.ts\t\n@@ -1,1 +1,1 @@\n-old\n\\ No newline at end of file\n+new\n\\ No newline at end of file\n" ,
2026-04-03 00:56:56 +00:00
additions : 2 ,
deletions : 0 ,
status : "modified" ,
} ,
] ,
} )
yield * Effect . sleep ( 1 _250 )
expect ( seen ) . toHaveLength ( 1 )
expect ( seen [ 0 ] . url ) . toBe ( "https://legacy-share.example.com/api/share/shr_abc/sync" )
const body = JSON . parse ( seen [ 0 ] . body ) as {
secret : string
data : Array < {
type : string
data : Array < {
file : string
2026-04-07 23:48:23 +00:00
patch : string
2026-04-03 00:56:56 +00:00
additions : number
deletions : number
status? : string
} >
} >
}
expect ( body . secret ) . toBe ( "sec_123" )
expect ( body . data ) . toHaveLength ( 1 )
expect ( body . data [ 0 ] . type ) . toBe ( "session_diff" )
expect ( body . data [ 0 ] . data ) . toEqual ( [
{
file : "b.ts" ,
2026-04-07 23:48:23 +00:00
patch :
"Index: b.ts\n===================================================================\n--- b.ts\t\n+++ b.ts\t\n@@ -1,1 +1,1 @@\n-old\n\\ No newline at end of file\n+new\n\\ No newline at end of file\n" ,
2026-04-03 00:56:56 +00:00
additions : 2 ,
deletions : 0 ,
status : "modified" ,
} ,
] )
} ) . pipe ( Effect . provide ( wired ( client ) ) )
} ,
{ config : { enterprise : { url : "https://legacy-share.example.com" } } } ,
) ,
)
2026-03-10 16:53:47 +00:00
} )