11// @ts -nocheck
2- import { beforeEach , describe , expect , mock , spyOn , test } from "bun:test" ;
2+ import { afterEach , beforeEach , describe , expect , spyOn , test } from "bun:test" ;
33// schemas/sessions.ts calls `.openapi()` on the @openagentpack/sdk core schemas at module-eval time. That
44// method is added to zod's prototype as a side effect of importing @hono/zod-openapi, so the core
55// schemas must be built on the SAME zod instance @hono /zod-openapi patched. IMPORTANT: do NOT
@@ -10,12 +10,15 @@ import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
1010import { z as zWithOpenApi } from "@hono/zod-openapi" ;
1111import { getPlaybookAppId , PLAYBOOK_APP_METADATA_KEY , PLAYBOOK_METADATA_KEY } from "@openagentpack/playbooks" ;
1212import * as actualCore from "@openagentpack/sdk" ;
13+ import * as runtimeFactory from "@/services/runtime-factory" ;
14+ import * as sessionRunner from "@/services/sessions/runner" ;
1315
1416if ( typeof ( zWithOpenApi . string ( ) as { openapi ?: unknown } ) . openapi !== "function" ) {
1517 throw new Error ( "@hono/zod-openapi did not patch zod with .openapi" ) ;
1618}
1719
1820const calls = {
21+ withAgentRuntime : [ ] as unknown [ ] ,
1922 listSessionsForAgent : [ ] as unknown [ ] ,
2023 getSessionDetail : [ ] as unknown [ ] ,
2124 listSessionEventsPage : [ ] as unknown [ ] ,
@@ -47,7 +50,7 @@ const state = {
4750 listCloudAgents : async ( ) => [ sampleCloudAgent ( ) ] ,
4851} ;
4952
50- mock . module ( "@/services/sessions/runner" , ( ) => ( {
53+ const sessionStubs = {
5154 listSessionsForAgent : async ( ...args : unknown [ ] ) => {
5255 calls . listSessionsForAgent . push ( args ) ;
5356 return state . listSessionsForAgent ( ...args ) ;
@@ -76,53 +79,41 @@ mock.module("@/services/sessions/runner", () => ({
7679 calls . updatePlaybookAgentModel . push ( args ) ;
7780 return state . updatePlaybookAgentModel ( ...args ) ;
7881 } ,
79- reconstructSessionBuffer : async ( ) => false ,
80- } ) ) ;
81-
82- mock . module ( "@/services/runtime-factory" , ( ) => ( {
83- loadServerRuntimeConfig : async ( ) => ( {
84- projectName : "project" ,
85- config : { } ,
86- stateBackend : { } ,
87- stateScope : { projectId : "project" } ,
88- } ) ,
89- loadAgentRuntimeInput : async ( agentId : string ) => ( {
90- projectName : "project" ,
91- config : { } ,
92- stateBackend : { } ,
93- stateScope : { projectId : "project" } ,
94- agentId,
95- } ) ,
96- withAgentRuntime : async ( agentId : string , fn : ( ctx : unknown , compiled : unknown ) => unknown ) => {
97- globalThis . __withAgentRuntimeCalls ??= [ ] ;
98- globalThis . __withAgentRuntimeCalls . push ( [ agentId ] ) ;
99- return fn (
100- { configPath : "/tmp/agents.yaml" } ,
101- { agentId, agent : { id : agentId , version : "1" } , agentConfigHash : "h" } ,
102- ) ;
103- } ,
104- } ) ) ;
105-
106- // Stub the single SDK function the agents route calls. Using spyOn (not mock.module) keeps
107- // @openagentpack /sdk on one zod instance so schemas/sessions.ts can attach OpenAPI names (see top note).
108- spyOn ( actualCore , "listAgentsWithReadiness" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
109- calls . listAgentsWithReadiness . push ( args ) ;
110- return state . listAgentsWithReadiness ( ...args ) ;
111- } ) ;
82+ } ;
11283
113- spyOn ( actualCore , "listCloudAgents" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
114- calls . listCloudAgents . push ( args ) ;
115- return state . listCloudAgents ( ...args ) ;
116- } ) ;
84+ const spies : Array < { mockRestore ( ) : void } > = [ ] ;
85+
86+ function installMocks ( ) {
87+ for ( const name of Object . keys ( sessionStubs ) ) {
88+ spies . push ( spyOn ( sessionRunner , name ) . mockImplementation ( sessionStubs [ name ] ) ) ;
89+ }
90+ spies . push (
91+ spyOn ( runtimeFactory , "withAgentRuntime" ) . mockImplementation ( async ( agentId , fn ) => {
92+ calls . withAgentRuntime . push ( [ agentId ] ) ;
93+ return fn (
94+ { configPath : "/tmp/agents.yaml" } ,
95+ { agentId, agent : { id : agentId , version : "1" } , agentConfigHash : "h" } ,
96+ ) ;
97+ } ) ,
98+ spyOn ( actualCore , "listAgentsWithReadiness" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
99+ calls . listAgentsWithReadiness . push ( args ) ;
100+ return state . listAgentsWithReadiness ( ...args ) ;
101+ } ) ,
102+ spyOn ( actualCore , "listCloudAgents" ) . mockImplementation ( async ( ...args : unknown [ ] ) => {
103+ calls . listCloudAgents . push ( args ) ;
104+ return state . listCloudAgents ( ...args ) ;
105+ } ) ,
106+ ) ;
107+ }
117108
118- // Import Hono routes (they use the mocked @/services/* and @openagentpack/sdk modules above)
109+ // Load real modules before installing per-test spies. Replacing a whole module
110+ // hides exports used by other suites that share Bun's module cache.
119111const { agentsRoute : agentsApp } = await import ( "../src/routes/agents" ) ;
120112const { sessionsRoute : sessionsApp } = await import ( "../src/routes/sessions" ) ;
121113
122114describe ( "API routes" , ( ) => {
123115 beforeEach ( ( ) => {
124116 for ( const key of Object . keys ( calls ) ) calls [ key ] . length = 0 ;
125- globalThis . __withAgentRuntimeCalls = [ ] ;
126117 state . listSessionsForAgent = async ( ) => ( { sessions : [ sampleSession ( ) ] , nextPageToken : undefined } ) ;
127118 state . getSessionDetail = async ( ) => ( { session : sampleSession ( ) , events : [ sampleProviderEvent ( ) ] } ) ;
128119 state . listSessionEventsPage = async ( ) => ( { events : [ sampleProviderEvent ( ) ] , eventsNextPageToken : undefined } ) ;
@@ -144,6 +135,11 @@ describe("API routes", () => {
144135 ] ;
145136 state . ensureAgentReady = async ( ) => ( { agentId : "bailian-cli" , status : "completed" , results : [ ] } ) ;
146137 state . listCloudAgents = async ( ) => [ sampleCloudAgent ( ) ] ;
138+ installMocks ( ) ;
139+ } ) ;
140+
141+ afterEach ( ( ) => {
142+ for ( const spy of spies . splice ( 0 ) ) spy . mockRestore ( ) ;
147143 } ) ;
148144
149145 test ( "GET /api/sessions returns the snake_case session list" , async ( ) => {
@@ -291,7 +287,7 @@ describe("API routes", () => {
291287 const body = await response . json ( ) ;
292288
293289 expect ( response . status ) . toBe ( 200 ) ;
294- expect ( globalThis . __withAgentRuntimeCalls ) . toEqual ( [ [ "bailian-cli" ] ] ) ;
290+ expect ( calls . withAgentRuntime ) . toEqual ( [ [ "bailian-cli" ] ] ) ;
295291 expect ( calls . listAgentsWithReadiness [ 0 ] [ 1 ] ) . toEqual ( { refresh : false } ) ;
296292 expect ( body . agents [ 0 ] . agent . id ) . toBe ( "bailian-cli" ) ;
297293 expect ( body . agents [ 0 ] . readiness . agentId ) . toBe ( "bailian-cli" ) ;
@@ -303,7 +299,7 @@ describe("API routes", () => {
303299
304300 expect ( response . status ) . toBe ( 200 ) ;
305301 // Resolved against the bootstrap agent runtime once (not a per-request agentId).
306- expect ( globalThis . __withAgentRuntimeCalls ) . toHaveLength ( 1 ) ;
302+ expect ( calls . withAgentRuntime ) . toHaveLength ( 1 ) ;
307303 expect ( calls . listCloudAgents [ 0 ] [ 1 ] ) . toEqual ( { prefix : "Agents/" , limit : 100 } ) ;
308304 expect ( body . agents [ 0 ] . id ) . toBe ( "agt_cloud_1" ) ;
309305 expect ( body . agents [ 0 ] . name ) . toBe ( "Agents/researcher" ) ;
0 commit comments