@@ -13,6 +13,7 @@ const loadDevicesMock = vi.hoisted(() => vi.fn(async () => undefined));
1313const loadHealthStateMock = vi . hoisted ( ( ) => vi . fn ( async ( ) => undefined ) ) ;
1414const loadNodesMock = vi . hoisted ( ( ) => vi . fn ( async ( ) => undefined ) ) ;
1515const subscribeSessionsMock = vi . hoisted ( ( ) => vi . fn ( async ( ) => undefined ) ) ;
16+ const syncUrlWithSessionKeyMock = vi . hoisted ( ( ) => vi . fn ( ) ) ;
1617const verifyPushMock = vi . hoisted ( ( ) => vi . fn ( async ( ) => undefined ) ) ;
1718
1819type GatewayClientMock = {
@@ -23,6 +24,16 @@ type GatewayClientMock = {
2324
2425const gatewayClients : GatewayClientMock [ ] = [ ] ;
2526
27+ function createDeferred ( ) {
28+ let resolve : ( ) => void = ( ) => undefined ;
29+ let reject : ( reason ?: unknown ) => void = ( ) => undefined ;
30+ const promise = new Promise < undefined > ( ( res , rej ) => {
31+ resolve = ( ) => res ( undefined ) ;
32+ reject = rej ;
33+ } ) ;
34+ return { promise, reject, resolve } ;
35+ }
36+
2637vi . mock ( "./gateway.ts" , async ( importOriginal ) => {
2738 const actual = await importOriginal < typeof import ( "./gateway.ts" ) > ( ) ;
2839
@@ -87,6 +98,7 @@ vi.mock("./app-settings.ts", () => ({
8798 loadCron : vi . fn ( ) ,
8899 refreshActiveTab : refreshActiveTabMock ,
89100 setLastActiveSessionKey : vi . fn ( ) ,
101+ syncUrlWithSessionKey : syncUrlWithSessionKeyMock ,
90102} ) ) ;
91103
92104vi . mock ( "./controllers/agents.ts" , ( ) => ( {
@@ -218,10 +230,112 @@ beforeEach(() => {
218230 loadHealthStateMock . mockClear ( ) ;
219231 loadNodesMock . mockClear ( ) ;
220232 subscribeSessionsMock . mockClear ( ) ;
233+ syncUrlWithSessionKeyMock . mockClear ( ) ;
221234 verifyPushMock . mockClear ( ) ;
222235} ) ;
223236
224237describe ( "connectGateway chat load startup work" , ( ) => {
238+ it ( "starts the active chat refresh before agents.list finishes" , async ( ) => {
239+ const agentsList = createDeferred ( ) ;
240+ loadAgentsMock . mockReturnValueOnce ( agentsList . promise ) ;
241+ const { host, client } = connectHost ( "chat" ) ;
242+
243+ client . emitHello ( ) ;
244+
245+ await vi . waitFor ( ( ) => expect ( refreshActiveTabMock ) . toHaveBeenCalledWith ( host ) ) ;
246+ expect ( loadAgentsMock ) . toHaveBeenCalledWith ( host ) ;
247+ expect ( refreshActiveTabMock ) . toHaveBeenCalledTimes ( 1 ) ;
248+
249+ agentsList . resolve ( ) ;
250+ await agentsList . promise ;
251+ await Promise . resolve ( ) ;
252+ expect ( refreshActiveTabMock ) . toHaveBeenCalledTimes ( 1 ) ;
253+ } ) ;
254+
255+ it ( "waits for agents.list when a stale agent session may need fallback" , async ( ) => {
256+ const agentsList = createDeferred ( ) ;
257+ const { host, client } = connectHost ( "chat" ) ;
258+ loadAgentsMock . mockImplementationOnce ( async ( ) => {
259+ await agentsList . promise ;
260+ host . agentsList = {
261+ defaultId : "new-default" ,
262+ mainKey : "main" ,
263+ scope : "global" ,
264+ agents : [ { id : "new-default" } ] ,
265+ } ;
266+ } ) ;
267+ host . sessionKey = "agent:old-default:main" ;
268+ host . agentsList = {
269+ defaultId : "old-default" ,
270+ mainKey : "main" ,
271+ scope : "global" ,
272+ agents : [ { id : "old-default" } ] ,
273+ } ;
274+
275+ client . emitHello ( {
276+ type : "hello-ok" ,
277+ protocol : 4 ,
278+ snapshot : {
279+ sessionDefaults : {
280+ defaultAgentId : "new-default" ,
281+ mainKey : "main" ,
282+ mainSessionKey : "agent:new-default:main" ,
283+ } ,
284+ } ,
285+ auth : { role : "operator" , scopes : [ ] } ,
286+ } ) ;
287+
288+ await vi . waitFor ( ( ) => expect ( loadAgentsMock ) . toHaveBeenCalledWith ( host ) ) ;
289+ expect ( refreshActiveTabMock ) . not . toHaveBeenCalled ( ) ;
290+
291+ agentsList . resolve ( ) ;
292+ await vi . waitFor ( ( ) => expect ( refreshActiveTabMock ) . toHaveBeenCalledWith ( host ) ) ;
293+ expect ( host . sessionKey ) . toBe ( "agent:new-default:main" ) ;
294+ expect ( refreshActiveTabMock ) . toHaveBeenCalledTimes ( 1 ) ;
295+ } ) ;
296+
297+ it ( "waits for agents.list before refreshing selected-global chat" , async ( ) => {
298+ const agentsList = createDeferred ( ) ;
299+ const { host, client } = connectHost ( "chat" ) ;
300+ loadAgentsMock . mockImplementationOnce ( async ( ) => {
301+ await agentsList . promise ;
302+ host . agentsList = {
303+ defaultId : "new-default" ,
304+ mainKey : "main" ,
305+ scope : "global" ,
306+ agents : [ { id : "new-default" } ] ,
307+ } ;
308+ } ) ;
309+ host . sessionKey = "global" ;
310+ host . agentsList = {
311+ defaultId : "old-default" ,
312+ mainKey : "main" ,
313+ scope : "global" ,
314+ agents : [ { id : "old-default" } ] ,
315+ } ;
316+
317+ client . emitHello ( {
318+ type : "hello-ok" ,
319+ protocol : 4 ,
320+ snapshot : {
321+ sessionDefaults : {
322+ defaultAgentId : "new-default" ,
323+ mainKey : "main" ,
324+ mainSessionKey : "agent:new-default:main" ,
325+ } ,
326+ } ,
327+ auth : { role : "operator" , scopes : [ ] } ,
328+ } ) ;
329+
330+ await vi . waitFor ( ( ) => expect ( loadAgentsMock ) . toHaveBeenCalledWith ( host ) ) ;
331+ expect ( refreshActiveTabMock ) . not . toHaveBeenCalled ( ) ;
332+
333+ agentsList . resolve ( ) ;
334+ await vi . waitFor ( ( ) => expect ( refreshActiveTabMock ) . toHaveBeenCalledWith ( host ) ) ;
335+ expect ( host . sessionKey ) . toBe ( "global" ) ;
336+ expect ( refreshActiveTabMock ) . toHaveBeenCalledTimes ( 1 ) ;
337+ } ) ;
338+
225339 it ( "lets the active chat refresh own avatar loading on initial chat hello" , async ( ) => {
226340 const { host, client } = connectHost ( "chat" ) ;
227341
0 commit comments