77 createStoreHelper ,
88 makeData ,
99 makeDataNoCookie ,
10+ makeCookie ,
1011} from '../test/testHelper.js'
1112
1213let { store, storePromise } = createStoreHelper ( )
@@ -233,6 +234,48 @@ test.serial('set with no stringify', async (t) => {
233234 t . is ( await storePromise . length ( ) , 0 )
234235} )
235236
237+ test . serial (
238+ 'ttl uses cookie.maxAge before cookie.expires and ttl fallback' ,
239+ async ( t ) => {
240+ // Choose distinct magnitudes so ordering is unambiguous: 2s < 30s < 90s
241+ const defaultTtl = 30_000
242+ ; ( { store, storePromise } = createStoreHelper ( { ttl : defaultTtl / 1000 } ) )
243+ const cookieMaxAge = makeCookie ( )
244+ const sid = 'ttl-precedence'
245+ cookieMaxAge . maxAge = 2_000
246+ const sessionData = { foo : 'ttl' , cookie : cookieMaxAge }
247+
248+ // @ts -ignore
249+ await storePromise . set ( sid , sessionData )
250+ const collection = await store . collectionP
251+ const doc = await collection . findOne ( { _id : sid } )
252+
253+ // separate cookie with only expires set to test precedence
254+ const cookieExpires = makeCookie ( )
255+ cookieExpires . maxAge = undefined
256+ cookieExpires . expires = new Date ( Date . now ( ) + 90_000 )
257+ const sid2 = 'ttl-precedence-expires'
258+ // @ts -ignore
259+ await storePromise . set ( sid2 , { foo : 'ttl2' , cookie : cookieExpires } )
260+ const doc2 = await collection . findOne ( { _id : sid2 } )
261+
262+ // remove both to test ttl fallback
263+ const sid3 = 'ttl-precedence-ttl'
264+ // @ts -ignore
265+ await storePromise . set ( sid3 , { foo : 'ttl3' } )
266+ const doc3 = await collection . findOne ( { _id : sid3 } )
267+
268+ const expMs = doc ?. expires ?. getTime ( ) ?? 0
269+ const expMs2 = doc2 ?. expires ?. getTime ( ) ?? 0
270+ const expMs3 = doc3 ?. expires ?. getTime ( ) ?? 0
271+
272+ t . true ( expMs > 0 && expMs2 > 0 && expMs3 > 0 )
273+ // ordering: maxAge (2s) < ttl fallback (30s) < cookie.expires (90s)
274+ t . true ( expMs < expMs3 )
275+ t . true ( expMs3 < expMs2 )
276+ }
277+ )
278+
236279test . serial ( 'clear preserves TTL index and is idempotent' , async ( t ) => {
237280 ; ( { store, storePromise } = createStoreHelper ( { autoRemove : 'native' } ) )
238281 const collection = await store . collectionP
@@ -548,6 +591,33 @@ test.serial('touch ops with touchAfter with touch', async (t) => {
548591 }
549592} )
550593
594+ test . serial (
595+ 'touchAfter throttle keeps updatedAt unchanged until threshold when timestamps on' ,
596+ async ( t ) => {
597+ ; ( { store, storePromise } = createStoreHelper ( {
598+ touchAfter : 1 ,
599+ timestamps : true ,
600+ } ) )
601+ const sid = 'touchAfter-timestamps'
602+ // @ts -ignore
603+ await storePromise . set ( sid , makeDataNoCookie ( ) )
604+ const collection = await store . collectionP
605+ const doc = await collection . findOne ( { _id : sid } )
606+ const initialUpdated = doc ?. updatedAt ?. getTime ( )
607+
608+ const sessionWithMeta = await storePromise . get ( sid )
609+ await storePromise . touch ( sid , sessionWithMeta as SessionData )
610+ const docNoUpdate = await collection . findOne ( { _id : sid } )
611+ t . is ( docNoUpdate ?. updatedAt ?. getTime ( ) , initialUpdated )
612+
613+ await new Promise ( ( resolve ) => setTimeout ( resolve , 1100 ) )
614+ const sessionWithMetaAfterWait = await storePromise . get ( sid )
615+ await storePromise . touch ( sid , sessionWithMetaAfterWait as SessionData )
616+ const docUpdated = await collection . findOne ( { _id : sid } )
617+ t . truthy ( ( docUpdated ?. updatedAt ?. getTime ( ) ?? 0 ) > ( initialUpdated ?? 0 ) )
618+ }
619+ )
620+
551621test . serial ( 'basic operation flow with crypto' , async ( t ) => {
552622 ; ( { store, storePromise } = createStoreHelper ( {
553623 crypto : { secret : 'secret' } ,
@@ -567,6 +637,90 @@ test.serial('basic operation flow with crypto', async (t) => {
567637 t . is ( sessions ?. length , 1 )
568638} )
569639
640+ test . serial ( 'crypto with stringify=false roundtrips raw objects' , async ( t ) => {
641+ ; ( { store, storePromise } = createStoreHelper ( {
642+ crypto : { secret : 'secret' } ,
643+ stringify : false ,
644+ collectionName : 'crypto-no-stringify' ,
645+ } ) )
646+ const sid = 'crypto-no-stringify'
647+ const payload = makeDataNoCookie ( )
648+ // @ts -ignore
649+ await storePromise . set ( sid , payload )
650+ const session = await storePromise . get ( sid )
651+ t . deepEqual ( session , payload )
652+ } )
653+
654+ test . serial (
655+ 'transformId stores and retrieves using transformed key' ,
656+ async ( t ) => {
657+ const transformId = ( sid : string ) => `t-${ sid } `
658+ ; ( { store, storePromise } = createStoreHelper ( { transformId } ) )
659+ const sid = 'transform-id'
660+ await storePromise . set ( sid , makeData ( ) )
661+ const collection = await store . collectionP
662+ const doc = await collection . findOne ( { _id : transformId ( sid ) } )
663+ t . truthy ( doc )
664+ const session = await storePromise . get ( sid )
665+ t . truthy ( session )
666+ }
667+ )
668+
669+ test . serial ( 'writeOperationOptions forwarded to updateOne' , async ( t ) => {
670+ const calls : any [ ] = [ ]
671+ const fakeCollection = {
672+ createIndex : ( ) => Promise . resolve ( ) ,
673+ updateOne : ( ...args : any [ ] ) => {
674+ calls . push ( args )
675+ return Promise . resolve ( { upsertedCount : 1 } )
676+ } ,
677+ }
678+ const fakeClient = {
679+ db : ( ) => ( { collection : ( ) => fakeCollection } ) ,
680+ close : ( ) => Promise . resolve ( ) ,
681+ }
682+
683+ const writeConcern = { w : 0 as const }
684+ const localStore = MongoStore . create ( {
685+ clientPromise : Promise . resolve ( fakeClient as unknown as MongoClient ) ,
686+ writeOperationOptions : writeConcern ,
687+ collectionName : 'wopts' ,
688+ dbName : 'wopts-db' ,
689+ } )
690+ await new Promise < void > ( ( resolve , reject ) =>
691+ localStore . set ( 'wopts' , makeData ( ) , ( err ) =>
692+ err ? reject ( err ) : resolve ( )
693+ )
694+ )
695+ t . true ( calls . length > 0 )
696+ const opts = calls [ 0 ] ?. [ 2 ]
697+ t . deepEqual ( opts ?. writeConcern , writeConcern )
698+ await localStore . close ( )
699+ } )
700+
701+ test . serial ( 'custom serializer error surfaces from set()' , async ( t ) => {
702+ const boom = new Error ( 'serialize-fail' )
703+ ; ( { store, storePromise } = createStoreHelper ( {
704+ serialize : ( ) => {
705+ throw boom
706+ } ,
707+ } ) )
708+ const sid = 'serializer-error'
709+ await t . throwsAsync ( ( ) => storePromise . set ( sid , makeData ( ) ) , {
710+ message : boom . message ,
711+ } )
712+ } )
713+
714+ test . serial ( 'corrupted JSON payload bubbles error on get' , async ( t ) => {
715+ ; ( { store, storePromise } = createStoreHelper ( ) )
716+ const collection = await store . collectionP
717+ await collection . insertOne ( {
718+ _id : 'corrupt-json' ,
719+ session : '{bad json' ,
720+ } )
721+ await t . throwsAsync ( ( ) => storePromise . get ( 'corrupt-json' ) )
722+ } )
723+
570724test . serial ( 'with touch after and get non-exist session' , async ( t ) => {
571725 ; ( { store, storePromise } = createStoreHelper ( {
572726 touchAfter : 10 ,
0 commit comments