@@ -6,33 +6,46 @@ import { registerRoutes, authMiddleware } from '../routes'; // Adjust path
66import { storage } from '../storage' ; // Adjust path
77import { type Snippet } from '@shared/schema' ; // Adjust path
88
9+ // Mock the storage module
10+ import admin from 'firebase-admin' ; // Import firebase-admin
11+
12+ // Mock firebase-admin
13+ jest . mock ( 'firebase-admin' , ( ) => ( {
14+ credential : {
15+ cert : jest . fn ( ) ,
16+ } ,
17+ initializeApp : jest . fn ( ) ,
18+ auth : ( ) => ( {
19+ verifyIdToken : jest . fn ( ) ,
20+ } ) ,
21+ } ) ) ;
22+
923// Mock the storage module
1024jest . mock ( '../storage' , ( ) => ( {
1125 storage : {
1226 getSnippets : jest . fn ( ) ,
1327 getSnippet : jest . fn ( ) ,
14- // Add any other storage methods if they get called by routes indirectly
15- // For GET /api/public/snippets/:id, incrementSnippetViewCount might be called
16- // Let's add it to avoid potential undefined function errors if the route calls it.
17- incrementSnippetViewCount : jest . fn ( ) . mockResolvedValue ( undefined ) ,
28+ getUser : jest . fn ( ) , // Add getUser to the mock
29+ upsertUser : jest . fn ( ) , // Add upsertUser if it's called
30+ incrementSnippetViewCount : jest . fn ( ) . mockResolvedValue ( undefined ) ,
1831 } ,
1932} ) ) ;
2033
21- // Mock the authMiddleware to prevent actual auth logic from running
34+ // Mock the authMiddleware
35+ // We will use the actual authMiddleware but mock its dependencies (firebase-admin, storage.getUser)
36+ // This is a change from the original approach of completely mocking out authMiddleware.
37+ // This allows us to test the middleware's logic more accurately.
38+ const originalRoutes = jest . requireActual ( '../routes' ) ;
2239jest . mock ( '../routes' , ( ) => {
23- const originalModule = jest . requireActual ( '../routes' ) ;
40+ const original = jest . requireActual ( '../routes' ) ;
2441 return {
25- ...originalModule ,
26- authMiddleware : jest . fn ( ( req , res , next ) => {
27- // Simulate an authenticated user if needed for other tests, but not for public routes
28- // req.user = { id: 'test-user', email: 'test@example .com' };
29- next ( ) ;
30- } ) ,
42+ ...original ,
43+ // authMiddleware is NOT mocked here anymore, we use the real one.
3144 } ;
3245} ) ;
3346
34- // Mock pool for health check, not strictly necessary for these tests but good practice
35- // if registerRoutes touches it.
47+
48+ // Mock pool for health check
3649jest . mock ( '../db' , ( ) => ( {
3750 pool : {
3851 connect : jest . fn ( ) . mockResolvedValue ( {
@@ -47,14 +60,122 @@ let app: Express;
4760
4861beforeEach ( async ( ) => { // Make beforeEach async if registerRoutes is async
4962 app = express ( ) ;
50- app . use ( express . json ( ) ) ; // Required for Express to parse JSON request bodies
51- await registerRoutes ( app ) ; // Register all routes, ensure this completes if it's async
63+ app . use ( express . json ( ) ) ;
64+ // Use originalRoutes.registerRoutes from the actual module
65+ await originalRoutes . registerRoutes ( app ) ;
5266
5367 // Clear mock history before each test
5468 ( storage . getSnippets as jest . Mock ) . mockClear ( ) ;
5569 ( storage . getSnippet as jest . Mock ) . mockClear ( ) ;
70+ ( storage . getUser as jest . Mock ) . mockClear ( ) ;
5671 ( storage . incrementSnippetViewCount as jest . Mock ) . mockClear ( ) ;
57- ( authMiddleware as jest . Mock ) . mockClear ( ) ;
72+ // Clear firebase-admin mocks
73+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockClear ( ) ;
74+ } ) ;
75+
76+ // Define mock users and snippets
77+ const user_A_id = 'user_A_id' ;
78+ const user_B_id = 'user_B_id' ;
79+ const user_C_id = 'user_C_id' ;
80+
81+ const mockUserA = { id : user_A_id , email : 'a@example.com' , displayName : 'User A' , photoURL : null , createdAt : new Date ( ) , updatedAt : new Date ( ) , username : 'usera' , githubId : null , bio : null , location : null , website : null } ;
82+ const mockUserB = { id : user_B_id , email : 'b@example.com' , displayName : 'User B' , photoURL : null , createdAt : new Date ( ) , updatedAt : new Date ( ) , username : 'userb' , githubId : null , bio : null , location : null , website : null } ;
83+ const mockUserC = { id : user_C_id , email : 'c@example.com' , displayName : 'User C' , photoURL : null , createdAt : new Date ( ) , updatedAt : new Date ( ) , username : 'userc' , githubId : null , bio : null , location : null , website : null } ;
84+
85+
86+ const snippet1 : Partial < Snippet > = { id : 1 , title : 'User A Snippet 1' , code : 'code1' , language : 'js' , userId : user_A_id , isPublic : false } ;
87+ const snippet2 : Partial < Snippet > = { id : 2 , title : 'User A Snippet 2' , code : 'code2' , language : 'ts' , userId : user_A_id , isPublic : false } ;
88+ const snippet3 : Partial < Snippet > = { id : 3 , title : 'User B Snippet 1' , code : 'code3' , language : 'py' , userId : user_B_id , isPublic : false } ;
89+ const publicSnippet : Partial < Snippet > = { id : 4 , title : 'Public Snippet' , code : 'public' , language : 'md' , userId : user_A_id , isPublic : true } ;
90+
91+ describe ( 'GET /api/snippets (User-Specific)' , ( ) => {
92+ it ( 'should return 401 if no token is provided' , async ( ) => {
93+ const response = await request ( app ) . get ( '/api/snippets' ) ;
94+ expect ( response . status ) . toBe ( 401 ) ;
95+ expect ( response . body . message ) . toContain ( 'Unauthorized' ) ;
96+ } ) ;
97+
98+ it ( 'should return snippets for User A when authenticated as User A' , async ( ) => {
99+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockResolvedValue ( { uid : user_A_id } ) ;
100+ ( storage . getUser as jest . Mock ) . mockResolvedValue ( mockUserA ) ;
101+ ( storage . getSnippets as jest . Mock ) . mockImplementation ( async ( filters ) => {
102+ if ( filters . userId === user_A_id ) {
103+ return [ snippet1 , snippet2 ] ;
104+ }
105+ return [ ] ;
106+ } ) ;
107+
108+ const response = await request ( app )
109+ . get ( '/api/snippets' )
110+ . set ( 'Authorization' , 'Bearer test-token-user-a' ) ;
111+
112+ expect ( response . status ) . toBe ( 200 ) ;
113+ expect ( response . body ) . toEqual ( [ snippet1 , snippet2 ] ) ;
114+ expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( { userId : user_A_id } ) ) ;
115+ expect ( admin . auth ( ) . verifyIdToken ) . toHaveBeenCalledWith ( 'test-token-user-a' ) ;
116+ expect ( storage . getUser ) . toHaveBeenCalledWith ( user_A_id ) ;
117+ } ) ;
118+
119+ it ( 'should return snippets for User B when authenticated as User B' , async ( ) => {
120+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockResolvedValue ( { uid : user_B_id } ) ;
121+ ( storage . getUser as jest . Mock ) . mockResolvedValue ( mockUserB ) ;
122+ ( storage . getSnippets as jest . Mock ) . mockImplementation ( async ( filters ) => {
123+ if ( filters . userId === user_B_id ) {
124+ return [ snippet3 ] ;
125+ }
126+ return [ ] ;
127+ } ) ;
128+
129+ const response = await request ( app )
130+ . get ( '/api/snippets' )
131+ . set ( 'Authorization' , 'Bearer test-token-user-b' ) ;
132+
133+ expect ( response . status ) . toBe ( 200 ) ;
134+ expect ( response . body ) . toEqual ( [ snippet3 ] ) ;
135+ expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( { userId : user_B_id } ) ) ;
136+ } ) ;
137+
138+ it ( 'should return an empty array for User C (who has no snippets) when authenticated as User C' , async ( ) => {
139+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockResolvedValue ( { uid : user_C_id } ) ;
140+ ( storage . getUser as jest . Mock ) . mockResolvedValue ( mockUserC ) ;
141+ ( storage . getSnippets as jest . Mock ) . mockImplementation ( async ( filters ) => {
142+ if ( filters . userId === user_C_id ) {
143+ return [ ] ;
144+ }
145+ return [ snippet1 , snippet2 , snippet3 ] ; // Should not happen if filter is correct
146+ } ) ;
147+
148+ const response = await request ( app )
149+ . get ( '/api/snippets' )
150+ . set ( 'Authorization' , 'Bearer test-token-user-c' ) ;
151+
152+ expect ( response . status ) . toBe ( 200 ) ;
153+ expect ( response . body ) . toEqual ( [ ] ) ;
154+ expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( { userId : user_C_id } ) ) ;
155+ } ) ;
156+
157+ it ( 'should return 401 if token is invalid' , async ( ) => {
158+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockRejectedValue ( new Error ( 'Invalid token' ) ) ;
159+
160+ const response = await request ( app )
161+ . get ( '/api/snippets' )
162+ . set ( 'Authorization' , 'Bearer invalid-token' ) ;
163+
164+ expect ( response . status ) . toBe ( 401 ) ;
165+ expect ( response . body . message ) . toContain ( 'Invalid token' ) ;
166+ } ) ;
167+
168+ it ( 'should return 404 if user from token is not found in storage' , async ( ) => {
169+ ( admin . auth ( ) . verifyIdToken as jest . Mock ) . mockResolvedValue ( { uid : 'nonexistent-user-id' } ) ;
170+ ( storage . getUser as jest . Mock ) . mockResolvedValue ( undefined ) ; // User not found
171+
172+ const response = await request ( app )
173+ . get ( '/api/snippets' )
174+ . set ( 'Authorization' , 'Bearer token-for-nonexistent-user' ) ;
175+
176+ expect ( response . status ) . toBe ( 404 ) ;
177+ expect ( response . body . message ) . toContain ( 'User not found' ) ;
178+ } ) ;
58179} ) ;
59180
60181describe ( 'Public API Snippet Routes' , ( ) => {
@@ -68,29 +189,39 @@ describe('Public API Snippet Routes', () => {
68189
69190 const response = await request ( app ) . get ( '/api/public/snippets' ) ;
70191
192+ const mockPublicSnippetsActual : Snippet [ ] = [ // Use actual Snippet type
193+ { id : 1 , title : 'Public Snippet 1' , isPublic : true , language : 'javascript' , code : '' , userId : user_A_id , createdAt : new Date ( ) , updatedAt : new Date ( ) , viewCount :0 , isFavorite : false , shareId : null , tags : [ ] } ,
194+ { id : 2 , title : 'Public Snippet 2' , isPublic : true , language : 'python' , code : '' , userId : user_B_id , createdAt : new Date ( ) , updatedAt : new Date ( ) , viewCount :0 , isFavorite : false , shareId : null , tags : [ ] } ,
195+ ] ;
196+ ( storage . getSnippets as jest . Mock ) . mockResolvedValue ( mockPublicSnippetsActual ) ;
197+
198+ const response = await request ( app ) . get ( '/api/public/snippets' ) ;
199+
71200 expect ( response . status ) . toBe ( 200 ) ;
72- expect ( response . body ) . toEqual ( mockPublicSnippets ) ;
73- expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( { // Use objectContaining for flexibility
201+ expect ( response . body ) . toEqual ( mockPublicSnippetsActual . map ( s => ( { ... s , createdAt : s . createdAt . toISOString ( ) , updatedAt : s . updatedAt . toISOString ( ) } ) ) ) ; // Adjust for date serialization
202+ expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( {
74203 isPublic : true ,
75204 } ) ) ;
76- expect ( authMiddleware ) . not . toHaveBeenCalled ( ) ; // Ensure authMiddleware is not called for this public route
205+ // authMiddleware is part of the actual routes, so it WILL be called by Express
206+ // but it won't find a token and thus won't authenticate for public routes.
207+ // The important thing is that the route handler itself doesn't *require* req.user.
77208 } ) ;
78209
79- it ( 'should pass query parameters (search, language, tag) to storage.getSnippets' , async ( ) => {
210+ it ( 'should pass query parameters (search, language, tag) to storage.getSnippets for public snippets ' , async ( ) => {
80211 ( storage . getSnippets as jest . Mock ) . mockResolvedValue ( [ ] ) ;
81- const query = { search : 'test' , language : 'javascript' , tag : 'example ' } ;
212+ const query = { search : 'public test' , language : 'javascript' , tag : 'public_example ' } ;
82213
83214 await request ( app ) . get ( '/api/public/snippets' ) . query ( query ) ;
84215
85- expect ( storage . getSnippets ) . toHaveBeenCalledWith ( {
216+ expect ( storage . getSnippets ) . toHaveBeenCalledWith ( expect . objectContaining ( { // objectContaining for flexibility
86217 isPublic : true ,
87218 search : query . search ,
88219 language : query . language ,
89220 tag : query . tag ,
90- } ) ;
221+ } ) ) ;
91222 } ) ;
92223
93- it ( 'should handle errors from storage.getSnippets gracefully' , async ( ) => {
224+ it ( 'should handle errors from storage.getSnippets gracefully for public snippets ' , async ( ) => {
94225 ( storage . getSnippets as jest . Mock ) . mockRejectedValue ( new Error ( 'Storage failure' ) ) ;
95226
96227 const response = await request ( app ) . get ( '/api/public/snippets' ) ;
@@ -103,22 +234,18 @@ describe('Public API Snippet Routes', () => {
103234
104235 describe ( 'GET /api/public/snippets/:id' , ( ) => {
105236 it ( 'should return a single public snippet if it exists and isPublic is true' , async ( ) => {
106- const mockSnippet : Partial < Snippet > = { id : 1 , title : 'Test Snippet' , isPublic : true , code : 'code' , language : 'js' } ;
107- ( storage . getSnippet as jest . Mock ) . mockResolvedValue ( mockSnippet ) ;
237+ const mockSnippetActual : Snippet = { id : 1 , title : 'Test Snippet' , isPublic : true , code : 'code' , language : 'js' , userId : user_A_id , createdAt : new Date ( ) , updatedAt : new Date ( ) , viewCount : 0 , isFavorite : false , shareId : null , tags : [ ] } ;
238+ ( storage . getSnippet as jest . Mock ) . mockResolvedValue ( mockSnippetActual ) ;
108239
109240 const response = await request ( app ) . get ( '/api/public/snippets/1' ) ;
110241
111242 expect ( response . status ) . toBe ( 200 ) ;
112- expect ( response . body ) . toEqual ( mockSnippet ) ;
113- expect ( storage . getSnippet ) . toHaveBeenCalledWith ( 1 ) ; // ID is number
114- // The route for /api/public/snippets/:id in the provided routes.ts does NOT call incrementSnippetViewCount.
115- // The /api/snippets/:id (non-public) and /api/shared/:shareId do.
116- // So, we should expect it NOT to be called here.
243+ expect ( response . body ) . toEqual ( { ...mockSnippetActual , createdAt : mockSnippetActual . createdAt . toISOString ( ) , updatedAt : mockSnippetActual . updatedAt . toISOString ( ) } ) ;
244+ expect ( storage . getSnippet ) . toHaveBeenCalledWith ( 1 ) ;
117245 expect ( storage . incrementSnippetViewCount ) . not . toHaveBeenCalled ( ) ;
118- expect ( authMiddleware ) . not . toHaveBeenCalled ( ) ;
119246 } ) ;
120247
121- it ( 'should return 404 if snippet is not found by storage.getSnippet' , async ( ) => {
248+ it ( 'should return 404 if public snippet is not found by storage.getSnippet' , async ( ) => {
122249 ( storage . getSnippet as jest . Mock ) . mockResolvedValue ( null ) ; // Snippet not found
123250
124251 const response = await request ( app ) . get ( '/api/public/snippets/999' ) ;
@@ -129,17 +256,16 @@ describe('Public API Snippet Routes', () => {
129256 } ) ;
130257
131258 it ( 'should return 404 if snippet is found but isPublic is false' , async ( ) => {
132- const mockPrivateSnippet : Partial < Snippet > = { id : 2 , title : 'Private Snippet' , isPublic : false , code : 'private' , language : 'js' } ;
133- ( storage . getSnippet as jest . Mock ) . mockResolvedValue ( mockPrivateSnippet ) ;
259+ const mockPrivateSnippetActual : Snippet = { id : 2 , title : 'Private Snippet' , isPublic : false , code : 'private' , language : 'js' , userId : user_A_id , createdAt : new Date ( ) , updatedAt : new Date ( ) , viewCount : 0 , isFavorite : false , shareId : null , tags : [ ] } ;
260+ ( storage . getSnippet as jest . Mock ) . mockResolvedValue ( mockPrivateSnippetActual ) ;
134261
135262 const response = await request ( app ) . get ( '/api/public/snippets/2' ) ;
136263
137264 expect ( response . status ) . toBe ( 404 ) ;
138- // Check against the actual error message in your route handler
139265 expect ( response . body ) . toEqual ( { message : "Snippet not found or not public" } ) ;
140266 } ) ;
141267
142- it ( 'should handle errors from storage.getSnippet gracefully' , async ( ) => {
268+ it ( 'should handle errors from storage.getSnippet gracefully for public snippets ' , async ( ) => {
143269 ( storage . getSnippet as jest . Mock ) . mockRejectedValue ( new Error ( 'Storage failure for single snippet' ) ) ;
144270
145271 const response = await request ( app ) . get ( '/api/public/snippets/1' ) ;
0 commit comments