11import mongoose from 'mongoose' ;
2- import { describe , it , expect } from 'vitest' ;
2+ import { describe , it , expect , afterEach , vi } from 'vitest' ;
33import { User } from './User' ;
44
55describe ( 'User Model' , ( ) => {
6- it ( 'is compiled properly and exposed' , ( ) => {
6+ it ( 'is compiled properly and exposed' , ( ) : void => {
77 expect ( User ) . toBeDefined ( ) ;
88 expect ( User . modelName ) . toBe ( 'User' ) ;
99 } ) ;
1010
1111 describe ( 'username schema constraints' , ( ) => {
12- it ( 'has lowercase: true on username path' , ( ) => {
12+ it ( 'has lowercase: true on username path' , ( ) : void => {
1313 const usernamePath = User . schema . path ( 'username' ) as mongoose . SchemaType & {
1414 options : Record < string , unknown > ;
1515 } ;
1616 expect ( usernamePath . options . lowercase ) . toBe ( true ) ;
1717 } ) ;
1818
1919 describe ( 'createdAt schema' , ( ) => {
20- it ( 'uses a callable default that returns a timestamp' , ( ) => {
20+ it ( 'uses a callable default that returns a timestamp' , ( ) : void => {
2121 const createdAtPath = User . schema . path ( 'createdAt' ) as mongoose . SchemaType & {
2222 options : { default ?: unknown } ;
2323 } ;
@@ -29,7 +29,7 @@ describe('User Model', () => {
2929 expect ( Number . isFinite ( result ) ) . toBe ( true ) ;
3030 } ) ;
3131
32- it ( 'has a defined defaultValue that is Date.now or returns a Date' , ( ) => {
32+ it ( 'has a defined defaultValue that is Date.now or returns a Date' , ( ) : void => {
3333 const createdAtPath = User . schema . path ( 'createdAt' ) as mongoose . SchemaType & {
3434 defaultValue ?: unknown ;
3535 options : { default ?: unknown } ;
@@ -47,21 +47,21 @@ describe('User Model', () => {
4747 } ) ;
4848 } ) ;
4949
50- it ( 'has trim: true on username path' , ( ) => {
50+ it ( 'has trim: true on username path' , ( ) : void => {
5151 const usernamePath = User . schema . path ( 'username' ) as mongoose . SchemaType & {
5252 options : Record < string , unknown > ;
5353 } ;
5454 expect ( usernamePath . options . trim ) . toBe ( true ) ;
5555 } ) ;
5656
57- it ( 'has unique: true on username path' , ( ) => {
57+ it ( 'has unique: true on username path' , ( ) : void => {
5858 const usernamePath = User . schema . path ( 'username' ) as mongoose . SchemaType & {
5959 options : Record < string , unknown > ;
6060 } ;
6161 expect ( usernamePath . options . unique ) . toBe ( true ) ;
6262 } ) ;
6363
64- it ( 'has required: true on username path' , ( ) => {
64+ it ( 'has required: true on username path' , ( ) : void => {
6565 const usernamePath = User . schema . path ( 'username' ) as mongoose . SchemaType & {
6666 options : Record < string , unknown > ;
6767 } ;
@@ -70,36 +70,101 @@ describe('User Model', () => {
7070 } ) ;
7171
7272 describe ( 'Database Connection State 2 Handling' , ( ) => {
73- it ( 'buffers operations when connection is in state 2 (connecting)' , async ( ) => {
74- const { vi } = await import ( 'vitest' ) ;
75- const readyStateSpy = vi
73+ let readyStateSpy : ReturnType < typeof vi . spyOn > | undefined ;
74+
75+ afterEach ( ( ) : void => {
76+ // Restore bufferCommands to default (true) on both mongoose connection settings and the User schema
77+ mongoose . set ( 'bufferCommands' , true ) ;
78+ User . schema . set ( 'bufferCommands' , true ) ;
79+
80+ // Clean up collection queue to avoid leaking buffered operations to other tests
81+ const collectionWrapper = User . collection as unknown as { queue : unknown [ ] } ;
82+ if ( collectionWrapper && Array . isArray ( collectionWrapper . queue ) ) {
83+ collectionWrapper . queue = [ ] ;
84+ }
85+
86+ // Restore active state spies to ensure they never leak into surrounding test suites
87+ if ( readyStateSpy ) {
88+ readyStateSpy . mockRestore ( ) ;
89+ readyStateSpy = undefined ;
90+ }
91+
92+ // Clear all mocks to ensure absolute test isolation
93+ vi . clearAllMocks ( ) ;
94+ } ) ;
95+
96+ it ( 'buffers operations when connection is in state 2 (connecting) by default' , async ( ) : Promise < void > => {
97+ // In connection state 2 (connecting), Mongoose buffers operations by default rather than throwing errors immediately.
98+ // This behavior occurs because Mongoose assumes the database connection will be established shortly (transitioning to state 1).
99+ // Therefore, it queues all pending model commands inside the internal collection queue (User.collection.queue).
100+ //
101+ // In contrast, in connection state 0 (disconnected), Mongoose will either throw a ConnectionError immediately (if command buffering is disabled)
102+ // or time out because there is no ongoing connection attempt that would eventually flush the command buffer.
103+ // In state 2, the query remains in-flight (pending) waiting for connection recovery/open events.
104+
105+ let currentReadyState = 2 ;
106+ readyStateSpy = vi
76107 . spyOn ( mongoose . connection , 'readyState' , 'get' )
77- . mockReturnValue ( 2 as unknown as typeof mongoose . connection . readyState ) ;
108+ . mockImplementation ( ( ) => currentReadyState as typeof mongoose . connection . readyState ) ;
78109
79- let operationAttempted = false ;
110+ expect ( mongoose . connection . readyState ) . toBe ( 2 ) ;
80111
81- const simulateBufferedOperation = async ( ) => {
82- if ( mongoose . connection . readyState === 2 ) {
83- operationAttempted = true ;
84- return 'buffered' ;
85- }
86- return 'executed' ;
87- } ;
112+ // Trigger a findOne operation which should transition smoothly into a buffered state
113+ const promise = User . findOne ( { username : 'testuser' } ) . exec ( ) ;
114+
115+ // Wait a microtask / tick for Mongoose to queue the collection operation
116+ await new Promise ( ( resolve ) => process . nextTick ( resolve ) ) ;
88117
89- const result = await simulateBufferedOperation ( ) ;
118+ // Assert that the command was successfully buffered in the collection wrapper queue
119+ const collectionWrapper = User . collection as unknown as { queue : unknown [ ] } ;
120+ expect ( collectionWrapper . queue . length ) . toBe ( 1 ) ;
121+
122+ // Assert that the returned promise is pending by racing it with a fast timeout
123+ const timeoutPromise = new Promise ( ( _ , reject ) =>
124+ setTimeout ( ( ) => reject ( new Error ( 'TIMEOUT' ) ) , 100 )
125+ ) ;
126+ await expect ( Promise . race ( [ promise , timeoutPromise ] ) ) . rejects . toThrow ( 'TIMEOUT' ) ;
127+
128+ // Simulate a successful connection transition to state 1 (connected)
129+ currentReadyState = 1 ;
130+ expect ( mongoose . connection . readyState ) . toBe ( 1 ) ;
131+
132+ // Drain the queued operations on the connection. The query is now executed against the underlying driver.
133+ // Since there is no active physical database instance in this mock, it will fail during driver call dispatch,
134+ // confirming that the query promise was successfully released from the buffer and attempted execution.
135+ try {
136+ ( mongoose . connection as unknown as { onOpen : ( ) => void } ) . onOpen ( ) ;
137+ await promise ;
138+ } catch ( error : unknown ) {
139+ expect ( error ) . toBeDefined ( ) ;
140+ expect ( error instanceof TypeError || error instanceof Error ) . toBe ( true ) ;
141+ }
142+ } ) ;
143+
144+ it ( 'rejects operations immediately when bufferCommands is disabled in state 2' , async ( ) : Promise < void > => {
145+ // Disable command buffering on both mongoose and User schema
146+ mongoose . set ( 'bufferCommands' , false ) ;
147+ User . schema . set ( 'bufferCommands' , false ) ;
148+
149+ readyStateSpy = vi
150+ . spyOn ( mongoose . connection , 'readyState' , 'get' )
151+ . mockReturnValue ( 2 as unknown as typeof mongoose . connection . readyState ) ;
90152
91153 expect ( mongoose . connection . readyState ) . toBe ( 2 ) ;
92- expect ( operationAttempted ) . toBe ( true ) ;
93- expect ( result ) . toBe ( 'buffered' ) ;
94154
95- readyStateSpy . mockRestore ( ) ;
155+ // When buffering is disabled, the operation should fail immediately rather than waiting or queuing.
156+ await expect ( User . findOne ( { username : 'testuser' } ) . exec ( ) ) . rejects . toThrow (
157+ / C a n n o t c a l l .* i f .* b u f f e r C o m m a n d s = f a l s e /
158+ ) ;
159+
160+ // Assert that nothing was added to the collection queue
161+ const collectionWrapper = User . collection as unknown as { queue : unknown [ ] } ;
162+ expect ( collectionWrapper . queue . length ) . toBe ( 0 ) ;
96163 } ) ;
97164 } ) ;
98165
99166 describe ( 'Database Connection State 0 Handling' , ( ) => {
100- it ( 'fails queries gracefully with a ConnectionError when disconnected' , async ( ) => {
101- const { vi } = await import ( 'vitest' ) ;
102-
167+ it ( 'fails queries gracefully with a ConnectionError when disconnected' , async ( ) : Promise < void > => {
103168 const readyStateSpy = vi
104169 . spyOn ( mongoose . connection , 'readyState' , 'get' )
105170 . mockReturnValue ( 0 as unknown as typeof mongoose . connection . readyState ) ;
@@ -124,9 +189,7 @@ describe('User Model', () => {
124189 } ) ;
125190
126191 describe ( 'Database Connection State 3 (Disconnecting) Handling' , ( ) => {
127- it ( 'aborts/rolls back active transactions cleanly when connection is in state 3 (disconnecting)' , async ( ) => {
128- const { vi } = await import ( 'vitest' ) ;
129-
192+ it ( 'aborts/rolls back active transactions cleanly when connection is in state 3 (disconnecting)' , async ( ) : Promise < void > => {
130193 const readyStateSpy = vi
131194 . spyOn ( mongoose . connection , 'readyState' , 'get' )
132195 . mockReturnValue ( 3 as unknown as typeof mongoose . connection . readyState ) ;
@@ -140,7 +203,9 @@ describe('User Model', () => {
140203
141204 const startSessionSpy = vi . spyOn ( mongoose , 'startSession' ) . mockResolvedValue ( mockSession ) ;
142205
143- const runTransactionWithCheck = async ( session : mongoose . ClientSession ) => {
206+ const runTransactionWithCheck = async (
207+ session : mongoose . ClientSession
208+ ) : Promise < { status : string } > => {
144209 session . startTransaction ( ) ;
145210 try {
146211 if ( mongoose . connection . readyState === 3 ) {
@@ -171,9 +236,7 @@ describe('User Model', () => {
171236 } ) ;
172237
173238 describe ( 'Database Connection State 99 Handling' , ( ) => {
174- it ( 'triggers lazy initialization exactly once and uses the correct connection URI' , async ( ) => {
175- const { vi } = await import ( 'vitest' ) ;
176-
239+ it ( 'triggers lazy initialization exactly once and uses the correct connection URI' , async ( ) : Promise < void > => {
177240 // 1. Mock readyState to 99 (uninitialized — no connection ever attempted)
178241 const readyStateSpy = vi
179242 . spyOn ( mongoose . connection , 'readyState' , 'get' )
@@ -185,7 +248,7 @@ describe('User Model', () => {
185248 const MONGO_URI = 'mongodb://localhost:27017/commitpulse' ;
186249
187250 // 3. Simulate the lazy init fallback — connects exactly once with correct URI
188- const lazyInit = async ( ) => {
251+ const lazyInit = async ( ) : Promise < void > => {
189252 if ( mongoose . connection . readyState === 99 ) {
190253 await mongoose . connect ( MONGO_URI ) ;
191254 }
0 commit comments