fix: make Google login and session recovery resilient

This commit is contained in:
2026-09-06 13:50:58 +03:00
parent 53903cbe61
commit 52fdb5fe17
4 changed files with 162 additions and 68 deletions
+75
View File
@@ -0,0 +1,75 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {getGoogleConfig,loginWithGoogle,restoreSession,setSession,getToken,getStoredUser} from '../src/services/auth.js';
const memory=new Map();globalThis.localStorage={getItem:key=>memory.get(key)||null,setItem:(key,value)=>memory.set(key,value),removeItem:key=>memory.delete(key)};
test('Google config distinguishes unavailable endpoint from explicitly unconfigured service',async()=>{
const original=globalThis.fetch;
try{
globalThis.fetch=async()=>({ok:false,json:async()=>({error:'Service unavailable'})});await assert.rejects(getGoogleConfig(),/Service unavailable/);
globalThis.fetch=async()=>({ok:true,json:async()=>({clientId:null})});assert.deepEqual(await getGoogleConfig(),{clientId:null});
globalThis.fetch=async()=>({ok:true,json:async()=>({})});await assert.rejects(getGoogleConfig(),/doğrulanamadı/);
}finally{globalThis.fetch=original;}
});
test('invalid or cancelled Google sign-in cannot create a browser session',async()=>{
const original=globalThis.fetch;memory.clear();
try{
globalThis.fetch=async()=>({ok:true,json:async()=>({success:true,token:'',user:{email:'qa@example.test'}})});
await assert.rejects(loginWithGoogle('synthetic'),/doğrulanamadı/);assert.equal(memory.size,0);
const controller=new AbortController();
globalThis.fetch=async()=>({ok:true,json:async()=>{controller.abort();return {success:true,token:'synthetic',user:{email:'qa@example.test'}};}});
await assert.rejects(loginWithGoogle('synthetic',{signal:controller.signal}));assert.equal(memory.size,0);
}finally{globalThis.fetch=original;}
});
test('restore preserves credentials on transient and malformed responses but expires rejected credentials',async()=>{
const original=globalThis.fetch;
try{
for(const response of [
{ok:false,status:503,json:async()=>({error:'Unavailable'})},
{ok:false,status:502,json:async()=>{throw new Error('HTML response');}},
{ok:true,status:200,json:async()=>({success:true,user:{}})},
]){
setSession('old-token',{email:'old@example.test'});globalThis.fetch=async()=>response;
assert.equal((await restoreSession()).email,'old@example.test');assert.equal(getToken(),'old-token');
}
for(const status of [401,403]){
setSession('old-token',{email:'old@example.test'});
globalThis.fetch=async()=>({ok:false,status,json:async()=>{throw new Error('Invalid JSON');}});
assert.equal(await restoreSession(),null);assert.equal(getToken(),null);
}
}finally{globalThis.fetch=original;memory.clear();}
});
test('late restoration cannot replace or clear a newer login',async()=>{
const original=globalThis.fetch;
try{
for(const ok of [true,false]){
setSession('old-token',{email:'old@example.test'});
globalThis.fetch=async()=>({ok,status:ok?200:401,json:async()=>{
setSession('new-token',{email:'new@example.test'});
return {success:ok,user:{email:'old@example.test'}};
}});
assert.equal((await restoreSession()).email,'new@example.test');
assert.equal(getToken(),'new-token');assert.equal(getStoredUser().email,'new@example.test');
}
}finally{globalThis.fetch=original;memory.clear();}
});
test('partial browser storage failure does not leave a usable half-session',async()=>{
const original=globalThis.fetch,storage=globalThis.localStorage;memory.clear();
try{
globalThis.localStorage={...storage,setItem:(key,value)=>{if(key==='ihaleist_user')throw new Error('Quota');storage.setItem(key,value);}};
globalThis.fetch=async()=>({ok:true,json:async()=>({success:true,token:'synthetic',user:{email:'qa@example.test'}})});
await assert.rejects(loginWithGoogle('synthetic'),/saklayamadı/);assert.equal(getToken(),null);
}finally{globalThis.fetch=original;globalThis.localStorage=storage;memory.clear();}
});
test('restoration timeout releases a stalled body without erasing the session',async t=>{
const original=globalThis.fetch;
t.mock.timers.enable({apis:['setTimeout']});
try{
setSession('old-token',{email:'old@example.test'});
let bodyStarted;
const started=new Promise(resolve=>{bodyStarted=resolve;});
globalThis.fetch=async()=>({ok:true,json:()=>{bodyStarted();return new Promise(()=>{});}});
const restoring=restoreSession();await started;t.mock.timers.tick(30000);
assert.equal((await restoring).email,'old@example.test');assert.equal(getToken(),'old-token');
}finally{globalThis.fetch=original;memory.clear();t.mock.timers.reset();}
});