import fs from 'node:fs'; import {test,expect} from '@playwright/test'; test('same-origin proxy preserves encoded storage paths, tokens, filters and never falls back to Supabase',async({page})=>{ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''})); await page.goto('/index.html');await page.addScriptTag({url:'/core/cloud-transport.js'}); const result=await page.evaluate(async()=>{ const calls=[],upstream='https://usfjwhztqoopzzfmfbis.supabase.co',proxy=location.origin+'/api/index.php'; const send=CateriumCloudTransport.create({upstream,proxy,fallbackProxy:'https://api.caterium.ru'}); window.fetch=async request=>{calls.push(request.url);return new Response('unavailable',{status:503})}; await send(upstream+'/rest/v1/sun_app_state?select=revision&workspace_id=eq.company'); const path='/storage/v1/object/sign/sun-chat/company/photo%20one.jpg',query='?token=a%2Bb%2Fc%3D&download=photo.jpg'; const media=CateriumCloudTransport.mediaUrl(upstream+path+query),url=new URL(media); return {calls,media,pathname:url.pathname,path:url.searchParams.get('__caterium_path'),token:url.searchParams.get('token'),download:url.searchParams.get('download'),external:CateriumCloudTransport.mediaUrl('https://example.invalid/image.jpg')}; }); expect(result.calls).toHaveLength(2);expect(result.calls.every(u=>!u.includes('.supabase.co'))).toBe(true); expect(new URL(result.calls[0]).searchParams.get('workspace_id')).toBe('eq.company'); expect(result.pathname).toBe('/api/index.php');expect(result.path).toBe('/storage/v1/object/sign/sun-chat/company/photo%20one.jpg');expect(result.token).toBe('a+b/c=');expect(result.download).toBe('photo.jpg'); expect(result.external).toBe('https://example.invalid/image.jpg'); }); test('revision polling reads only changed bases and ignores the previous account response',async({page})=>{ await syncHarness(page); const result=await page.evaluate(async()=>{ let revision=1,readCount=0,resolveRevision;const calls=[]; const payload={format:'sun-cloud-v2',version:2,storage:{sunOrders:{t:'j',v:[]}}}; const query={select(fields){calls.push(fields);return this},eq(key,value){calls.push([key,value]);return this},async maybeSingle(){readCount++;return revision===99?new Promise(r=>resolveRevision=r):{data:{revision}}}}; const c={from(table){calls.push(table);return query},async rpc(name){calls.push(name);return {data:[{revision,payload}]}}}; SunCloudV2.testInit(c);await SunCloudV2.testBaseline({revision:1,payload}); await SunCloudV2.testPoll();const unchanged=calls.splice(0); revision=2;await SunCloudV2.testPoll();const changed=calls.splice(0); revision=99;const pending=SunCloudV2.testPoll();await SunCloudV2.testPoll();SunCloudV2.testLeave();resolveRevision({data:{revision:99}});await pending; return {unchanged,changed,late:calls,readCount}; }); expect(result.unchanged).toEqual(['sun_app_state','revision',['workspace_id','company']]); expect(result.changed).toContain('sun_fetch_app_state'); expect(result.late).not.toContain('sun_fetch_app_state');expect(result.readCount).toBe(3); }); test('Safari without streaming uploads can send login and binary bodies through fallback',async({page})=>{ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''})); await page.goto('/index.html');await page.addScriptTag({url:'/core/cloud-transport.js'}); const result=await page.evaluate(async()=>{ const NativeRequest=window.Request; window.Request=new Proxy(NativeRequest,{construct(Target,args){if(args[1]?.body instanceof ReadableStream)throw new TypeError('ReadableStream uploading is not supported');return new Target(...args)}}); const calls=[]; window.fetch=async request=>{calls.push({url:request.url,body:[...new Uint8Array(await request.arrayBuffer())],type:request.headers.get('content-type')});return request.url.includes('proxy.example')?new Response('',{status:503}):new Response('{}',{headers:{'content-type':'application/json'}})}; const send=CateriumCloudTransport.create({upstream:'https://backend.example',proxy:'https://proxy.example',fallbackProxy:'https://secondary.example',cooldown:0}); const body=JSON.stringify({email:'test@example.invalid',password:'test-password'}); await send('https://backend.example/auth/v1/token?grant_type=password',{method:'POST',headers:{'content-type':'application/json'},body}); await send('https://backend.example/storage/v1/object/test/image',{method:'POST',headers:{'content-type':'image/png'},body:new Uint8Array([0,255,137,80]).buffer}); return {calls,expected:[...new TextEncoder().encode(body)]}; }); expect(result.calls).toHaveLength(3); expect(result.calls[0].body).toEqual(result.expected);expect(result.calls[1].body).toEqual(result.expected); expect(result.calls[2].body).toEqual([0,255,137,80]);expect(result.calls[2].type).toBe('image/png'); }); async function delayedSessionApp(page,signedIn=true){ await page.route('https://**',r=>r.abort()); await page.addInitScript(signed=>{ localStorage.setItem('sunCloudV2Config',JSON.stringify({workspaceId:signed?'startup-company':'',localWorkspaceId:signed?'startup-company':'',tenantStorageReady:true,autoSync:false})); if(signed)localStorage.setItem('sb-startup-auth-token','stored-session-placeholder-without-credentials'); localStorage.setItem('sunEnterpriseSettingsV1',JSON.stringify({rolesEnabled:true})); localStorage.setItem('sunUsersV1',JSON.stringify([{id:'legacy-user',name:'Old local user',role:'owner',active:true,pinHash:'old-hash'}])); window.supabase={createClient:()=>({ auth:{onAuthStateChange:()=>({data:{subscription:{unsubscribe(){}}}}),getSession:()=>new Promise(resolve=>{window.finishSessionRestore=resolve}),getUser:async()=>({data:{user:null}})}, rpc:async name=>({data:name==='sun_my_workspaces'?[{id:'startup-company',name:'Test company',role:'admin',is_active:true}]:null}), channel:()=>({on(){return this},subscribe(){return this}}),removeChannel:()=>{} })}; window.loginFormSeen=false;window.legacyFormSeen=false;new MutationObserver(()=>{if(document.getElementById('sunGateEmailV3'))window.loginFormSeen=true;if(document.getElementById('sunLoginOverlay'))window.legacyFormSeen=true}).observe(document,{childList:true,subtree:true}); },signedIn); await page.goto('/index.html',{waitUntil:'domcontentloaded'}); await page.waitForFunction(()=>Boolean(window.finishSessionRestore)); } test('restoring an existing session never mounts a password form before opening the app',async({page})=>{ await delayedSessionApp(page); await expect(page.locator('#sunCloudAuthGateV3')).toHaveAttribute('data-auth-state','restoring'); await expect(page.locator('#sunGateEmailV3')).toHaveCount(0);await expect(page.locator('body > header')).toBeHidden(); await page.evaluate(()=>finishSessionRestore({data:{session:{user:{id:'startup-user',email:'test@example.invalid'}}},error:null})); await expect(page.locator('#sunCloudAuthGateV3')).toHaveCount(0);await expect(page.locator('body > header')).toBeVisible(); expect(await page.evaluate(()=>window.loginFormSeen)).toBe(false); expect(await page.evaluate(()=>window.legacyFormSeen)).toBe(false); }); test('the single current login is styled even when its decorative script is unavailable',async({page})=>{ await page.route('**/core/login-signature-v1776.js*',r=>r.abort()); await delayedSessionApp(page,false); await page.evaluate(()=>finishSessionRestore({data:{session:null},error:null})); await expect(page.locator('#sunGateEmailV3')).toBeVisible(); await expect(page.locator('#sunGateTitleV3')).toHaveText('Войти в рабочее пространство'); await expect(page.locator('#sunCloudAuthGateV3 .caterium-signature-brand')).toHaveCount(1); await expect(page.locator('#sunCloudAuthGateV3 .sun-cloud-auth-card')).toHaveCSS('background-color','rgba(0, 0, 0, 0)'); await expect(page.locator('#sunGateEmailV3')).toHaveCSS('border-radius','13px'); await expect(page.locator('#sunLoginOverlay,#cateriumAuthBootV1776')).toHaveCount(0); await page.locator('#sunGateSwitchV27').click();await expect(page.locator('#sunGateTitleV3')).toHaveText('Создать аккаунт Caterium'); await page.locator('#sunGateSwitchV27').click();await expect(page.locator('#sunGateTitleV3')).toHaveText('Войти в рабочее пространство'); }); test('slow connections use a healthy route, allow longer saves and preserve caller cancellation',async({page})=>{ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''})); await page.goto('/index.html');await page.addScriptTag({url:'/core/cloud-transport.js'}); const result=await page.evaluate(async()=>{ const upstream='https://backend.example.invalid',proxy='https://proxy.example.invalid',calls=[]; const ok=()=>new Response('{}',{headers:{'content-type':'application/json'}}); window.fetch=(request,{signal})=>{calls.push(request.url);return request.url.startsWith(proxy)?new Promise((_,reject)=>signal.addEventListener('abort',()=>reject(signal.reason),{once:true})):Promise.resolve(ok())}; const send=CateriumCloudTransport.create({upstream,proxy,fallbackProxy:'https://secondary.example.invalid',timeout:10,fallbackTimeout:100,writeTimeout:100}); await send(upstream+'/rest/v1/rpc/sun_my_workspaces',{method:'POST',body:'{}'}); await send(upstream+'/rest/v1/rpc/sun_fetch_app_state',{method:'POST',body:'{}'}); const routes=calls.splice(0); window.fetch=(request,{signal})=>{calls.push(request.url);return new Promise((resolve,reject)=>{const timer=setTimeout(()=>resolve(ok()),35);signal.addEventListener('abort',()=>{clearTimeout(timer);reject(signal.reason)},{once:true})})}; const slowSave=CateriumCloudTransport.create({upstream,proxy,fallbackProxy:'https://secondary.example.invalid',timeout:10,writeTimeout:100}); const saved=(await slowSave(upstream+'/rest/v1/rpc/sun_save_app_state_v17',{method:'POST',body:'{}'})).status;const saveCalls=calls.splice(0); const timeoutSend=CateriumCloudTransport.create({upstream,proxy,fallbackProxy:'https://secondary.example.invalid',writeTimeout:5});let timeoutName=''; try{await timeoutSend(upstream+'/rest/v1/rpc/sun_save_app_state_v17',{method:'POST',body:'{}'})}catch(e){timeoutName=e.name}const timeoutCalls=calls.splice(0); const controller=new AbortController();controller.abort(new DOMException('Account changed','AbortError'));let cancel=''; try{await send(upstream+'/rest/v1/rpc/sun_my_workspaces',{method:'POST',body:'{}',signal:controller.signal})}catch(e){cancel=e.message} return {routes,saved,saveCalls,timeoutName,timeoutCalls,cancel,cancelCalls:calls}; }); expect(result.routes.map(u=>new URL(u).host)).toEqual(['proxy.example.invalid','secondary.example.invalid','secondary.example.invalid']); expect(result.saved).toBe(200);expect(result.saveCalls).toHaveLength(1);expect(result.timeoutName).toBe('TimeoutError');expect(result.timeoutCalls).toHaveLength(1); expect(result.cancel).toBe('Account changed');expect(result.cancelCalls).toHaveLength(0); }); async function syncHarness(page){ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''}));await page.goto('/index.html'); await page.addScriptTag({url:'/core/sun-safe.js'});await page.addScriptTag({url:'/core/cloud-transport.js'}); const runtime=fs.readFileSync('public/app-runtime.js','utf8');let source=runtime.slice(runtime.indexOf('/* ===== MODULE: cloud-sync-v2.js'),runtime.indexOf('/* ===== MODULE: admin-rbac-v3.js')); source=source.replace('async function boot(){','async function boot(){return;').replace('window.SunCloudV2={',`window.SunCloudV2={testPoll:pollRemoteChanges,testUploadDataUrl:uploadDataUrl,testInit:c=>{client=c;session={user:{id:'test-user'}};workspace={id:'company',role:'admin'};config.migrated.company=true;config.tenantStorageReady=true;config.localWorkspaceId='company';config.workspaceId='company'},testBaseline:setBaseline,testLeave:()=>{session=null;workspace=null},`); await page.addScriptTag({content:'var orders=[],boxes=[];'+source}); } test('cloud photos use raw bytes rather than multipart consumed by the PHP proxy',async({page})=>{ await syncHarness(page); const result=await page.evaluate(async()=>{ let uploaded; SunCloudV2.testInit({storage:{from:()=>({upload:async(path,body,options)=>{uploaded={path,raw:body instanceof ArrayBuffer,bytes:[...new Uint8Array(body)],options};return {error:null}}})}}); await SunCloudV2.testUploadDataUrl('data:image/png;base64,AP+JUA==');return uploaded; }); expect(result.raw).toBe(true);expect(result.bytes).toEqual([0,255,137,80]);expect(result.options.contentType).toBe('image/png');expect(result.path).toMatch(/^company\/media\//); }); test('an uncertain save is recovered by reading its committed revision without a duplicate write',async({page})=>{ await syncHarness(page);await page.clock.install(); await page.evaluate(async()=>{ window.calls=[];window.toasts=[];window.SunEnterprise={toast:message=>window.toasts.push(message)}; const payload={format:'sun-cloud-v2',version:2,storage:{sunOrders:{t:'j',v:[]}}};let row={payload,revision:1}; localStorage.setItem('sunOrders',JSON.stringify([{id:'test-order',total:2200}])); SunCloudV2.testInit({rpc:async(name,args)=>{calls.push(name);if(name==='sun_fetch_app_state')return {data:[structuredClone(row)]};if(name==='sun_save_app_state_v17'){row={payload:structuredClone(args.p_payload),revision:2};return {error:{message:'TimeoutError: server response lost'}}}throw new Error(name)}}); await SunCloudV2.testBaseline({payload,revision:1});await SunCloudV2.syncNow(); }); expect(await page.evaluate(()=>SunCloudV2.status().lastStatus)).toBe('pending'); await page.clock.fastForward(2100); await expect.poll(()=>page.evaluate(()=>SunCloudV2.status().lastStatus)).toBe('ready'); const result=await page.evaluate(()=>({calls,toasts,orders:JSON.parse(localStorage.sunOrders)})); expect(result.calls.filter(x=>x==='sun_fetch_app_state')).toHaveLength(2);expect(result.calls.filter(x=>x==='sun_save_app_state_v17')).toHaveLength(1); expect(result.toasts).toEqual([]);expect(result.orders).toEqual([{id:'test-order',total:2200}]); }); test('a late sync response cannot apply the previous account data after sign-out',async({page})=>{ await syncHarness(page); const result=await page.evaluate(async()=>{ window.toasts=[];window.SunEnterprise={toast:message=>toasts.push(message)};let resolve; SunCloudV2.testInit({rpc:()=>new Promise(r=>resolve=r)}); const pending=SunCloudV2.syncNow();SunCloudV2.testLeave();resolve({data:[{revision:2,payload:{storage:{sunOrders:{t:'j',v:[{id:'previous-user'}]}}}}]});await pending; return {orders:localStorage.getItem('sunOrders'),toasts,workspace:SunCloudV2.getWorkspace()}; }); expect(result.orders).toBeNull();expect(result.toasts).toEqual([]);expect(result.workspace).toBeNull(); }); test('cloud reads and password login recover from empty proxy responses without replaying writes',async({page})=>{ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''})); await page.goto('/index.html');await page.addScriptTag({url:'/core/cloud-transport.js'}); const result=await page.evaluate(async()=>{ const calls=[],upstream='https://backend.example.invalid',proxy='https://proxy.example.invalid';let scenario='read'; window.fetch=async request=>{calls.push({url:request.url,body:await request.text(),auth:request.headers.get('authorization')});if(scenario==='denied')return new Response('{"error":"denied"}',{status:401});if(scenario==='write')return new Response('',{status:503});return request.url.startsWith(proxy)?new Response('',{headers:{'content-type':'text/html'}}):new Response('[{"id":"company"}]',{headers:{'content-type':'application/json'}})}; const send=window.CateriumCloudTransport.create({upstream,proxy,fallbackProxy:'https://secondary.example.invalid',cooldown:0}); const read=await (await send(upstream+'/rest/v1/rpc/sun_my_workspaces',{method:'POST',headers:{Authorization:'Bearer test-token'},body:'{}'})).json(); const readCalls=calls.splice(0); await send(upstream+'/auth/v1/token?grant_type=password',{method:'POST',body:'{"email":"test@example.invalid","password":"test"}'});const authCalls=calls.splice(0); scenario='write';const write=await send(upstream+'/rest/v1/rpc/sun_save_app_state_v17',{method:'POST',body:'{}'});const writeCalls=calls.splice(0); scenario='empty-write';let writeError='';try{await send(upstream+'/rest/v1/rpc/sun_save_app_state_v17',{method:'POST',body:'{}'})}catch(e){writeError=e.message}const emptyWriteCalls=calls.splice(0); scenario='denied';const denied=await send(upstream+'/auth/v1/token?grant_type=password',{method:'POST',body:'{}'});const deniedCalls=calls.splice(0); return {read,readCalls,authCalls,write:write.status,writeCalls,writeError,emptyWriteCalls,denied:denied.status,deniedCalls}; }); expect(result.read).toEqual([{id:'company'}]);expect(result.readCalls.map(c=>c.url)).toEqual(['https://proxy.example.invalid/rest/v1/rpc/sun_my_workspaces','https://secondary.example.invalid/rest/v1/rpc/sun_my_workspaces']); expect(result.readCalls.every(c=>c.auth==='Bearer test-token'&&c.body==='{}')).toBe(true); expect(result.authCalls).toHaveLength(2);expect(result.authCalls[0].body).toBe(result.authCalls[1].body); expect(result.write).toBe(503);expect(result.writeCalls).toHaveLength(1);expect(result.writeError).toContain('пустой');expect(result.emptyWriteCalls).toHaveLength(1); expect(result.denied).toBe(401);expect(result.deniedCalls).toHaveLength(1); }); test('membership loading is shared and an invalid response remains an error until retry succeeds',async({page})=>{ await page.route('**/index.html',r=>r.fulfill({contentType:'text/html',body:''}));await page.goto('/index.html'); await page.addScriptTag({url:'/core/sun-safe.js'});await page.addScriptTag({url:'/core/cloud-transport.js'}); const runtime=fs.readFileSync('public/app-runtime.js','utf8');let cloud=runtime.slice(runtime.indexOf('/* ===== MODULE: cloud-sync-v2.js'),runtime.indexOf('/* ===== MODULE: admin-rbac-v3.js')); cloud=cloud.replace('async function boot(){','async function boot(){return;').replace('window.SunCloudV2={',`window.SunCloudV2={testLoad:loadMemberships,testInit:c=>{client=c;session={user:{id:'test-user'}};config.tenantStorageReady=true;config.localWorkspaceId='company';config.workspaceId='company'},`); await page.addScriptTag({content:'var orders=[];var boxes=[];'+cloud}); const result=await page.evaluate(async()=>{ let calls=0,resolve;const c=window.SunCloudV2;c.testInit({rpc:()=>{calls++;return new Promise(r=>resolve=r)}}); const a=c.testLoad(),b=c.testLoad();resolve({data:'',error:null});await Promise.all([a,b]);const failed=c.status(); const retry=c.testLoad();resolve({data:[{id:'company',name:'Test',role:'admin',is_active:true}],error:null});await retry; return {calls,failed,ready:c.status()}; }); expect(result.calls).toBe(2);expect(result.failed.membershipsLoading).toBe(false);expect(result.failed.membershipError).toContain('список компаний');expect(result.failed.workspace).toBeNull(); expect(result.ready.membershipError).toBe('');expect(result.ready.workspace.id).toBe('company');expect(result.ready.membershipsLoading).toBe(false); }); test('a failed company load replaces the stale login form with an actionable retry screen',async({page})=>{ await page.route('**://api.caterium.ru/**',r=>r.abort());await page.route('**://*.supabase.co/**',r=>r.abort()); await page.goto('/index.html',{waitUntil:'domcontentloaded'});await expect(page.locator('#sunGateEmailV3')).toBeVisible(); await page.evaluate(()=>{ window.loginState={connected:true,signedIn:true,membershipsLoading:true,membershipsLoaded:false};window.retryCount=0; window.SunCloudV2={getSession:()=>({user:{id:'test',email:'test@example.invalid'}}),getWorkspace:()=>null,getClient:()=>null,status:()=>window.loginState,hasPermission:()=>false,reloadMemberships:async()=>{window.retryCount++;return null}}; window.dispatchEvent(new Event('sun:cloud-permissions-changed')); }); await expect(page.locator('#sunGateEmailV3')).toHaveCount(0); await page.evaluate(()=>{window.loginState={...window.loginState,membershipsLoading:false,membershipsLoaded:true,membershipError:'Ответ сервера не получен'};window.dispatchEvent(new Event('sun:cloud-permissions-changed'))}); await expect(page.getByRole('heading',{name:'Не удалось загрузить рабочую базу'})).toBeVisible();await expect(page.locator('#sunGateErrorV3')).toHaveText('Ответ сервера не получен'); await page.getByRole('button',{name:'Повторить загрузку',exact:true}).click();expect(await page.evaluate(()=>window.retryCount)).toBe(1); await expect(page.getByRole('button',{name:'Повторить загрузку',exact:true})).toBeEnabled();await expect(page.locator('body > header')).toBeHidden(); }); for(const primaryFails of [false,true])test('real SDK loads orders with foreign services blocked, primary failure='+primaryFails,async({page})=>{ const userId='11111111-1111-4111-8111-111111111111',workspaceId='22222222-2222-4222-8222-222222222222',expires=Math.floor(Date.now()/1000)+3600; const user={id:userId,aud:'authenticated',role:'authenticated',email:'test@example.invalid',email_confirmed_at:new Date().toISOString(),app_metadata:{provider:'email'},user_metadata:{}}; const token=[{alg:'HS256',typ:'JWT'},{sub:userId,role:'authenticated',aud:'authenticated',exp:expires,iat:expires-3600,aal:'aal1'},'test'].map(x=>typeof x==='string'?x:Buffer.from(JSON.stringify(x)).toString('base64url')).join('.'); const seen=[],foreign=[],sockets=[],warnings=[]; page.on('console',m=>{if(m.type()==='warning')warnings.push(m.text())});page.on('websocket',ws=>sockets.push(ws.url())); await page.route('https://**',r=>{foreign.push(r.request().url());return r.abort()}); const handle=async route=>{ const request=route.request(),url=new URL(request.url()),path=url.searchParams.get('__caterium_path')||url.pathname;seen.push({host:url.host,path,method:request.method()}); const headers={'access-control-allow-origin':'*'}; if(request.method()==='OPTIONS')return route.fulfill({status:204,headers}); if(primaryFails&&url.pathname==='/api/index.php')return route.fulfill({status:200,contentType:'text/html',body:'',headers}); let body=null; if(path==='/auth/v1/token')body={access_token:token,refresh_token:'test-refresh',token_type:'bearer',expires_in:3600,expires_at:expires,user}; else if(path==='/auth/v1/user')body=user; else if(path.endsWith('/sun_my_workspaces'))body=[{id:workspaceId,name:'Test workspace',role:'admin',is_active:true,permissions:{}}]; else if(path.endsWith('/sun_is_platform_admin'))body=false; else if(path.endsWith('/sun_fetch_app_state'))body=[{revision:1,payload:{format:'sun-cloud-v2',version:2,storage:{sunOrders:{t:'j',v:[{id:'proxy-order',event:'Заказ без VPN',contact:'Тестовый клиент',phone:'79990000000',address:'Тестовый адрес',total:2400,lines:[]}]},sunBoxes:{t:'j',v:[]}}}}]; else if(path.endsWith('/sun_app_state'))body={revision:1}; return route.fulfill({status:200,contentType:'application/json',body:JSON.stringify(body),headers}); }; await page.route('**://api.caterium.ru/**',handle);await page.route('**/api/index.php?**',handle); await page.goto('/index.html',{waitUntil:'domcontentloaded'});await page.waitForFunction(()=>window.CateriumAuthSecurityV1774&&window.SunCloudV2?.getClient()); await page.locator('#sunGateEmailV3').fill(user.email);await page.locator('#sunGatePasswordV3').fill('test-password');await page.locator('#sunGateSubmitV3').click(); await expect(page.locator('#sunCloudAuthGateV3')).toHaveCount(0,{timeout:20000});await expect(page.locator('body > header')).toBeVisible(); expect(await page.evaluate(()=>window.SunCloudV2.getWorkspace()?.id)).toBe(workspaceId); await expect.poll(()=>page.evaluate(()=>JSON.parse(localStorage.getItem('sunOrders')||'[]').map(o=>o.id))).toContain('proxy-order'); expect(seen.some(s=>s.path==='/auth/v1/token'&&s.host!=='api.caterium.ru')).toBe(true); expect(seen.some(s=>s.host==='api.caterium.ru')).toBe(primaryFails); expect(foreign).toEqual([]);expect(sockets).toEqual([]); expect(warnings.some(s=>s.includes('Multiple GoTrueClient'))).toBe(false); });