@@ -4,6 +4,8 @@ const mockFind = jest.fn();
44const mockAnd = jest . fn ( ) ;
55const mockFindOne = jest . fn ( ) ;
66const mockQueryLean = jest . fn ( ) . mockResolvedValue ( [ ] ) ;
7+ const mockPopulate = jest . fn ( ) ;
8+ const mockEnginePopulate = jest . fn ( ) ;
79
810// mockAnd returns an object with .lean() so features.query.lean() works after .and()
911mockAnd . mockReturnValue ( { lean : mockQueryLean } ) ;
@@ -13,6 +15,7 @@ const mockQueryEngine = jest.fn((query) => {
1315 query,
1416 filter ( ) { return engine ; } ,
1517 sort ( ) { return engine ; } ,
18+ populate ( ) { mockEnginePopulate ( ...arguments ) ; return engine ; } ,
1619 paginate ( ) { return engine ; } ,
1720 } ;
1821 return engine ;
@@ -25,11 +28,25 @@ jest.mock('@urbackend/common', () => ({
2528 getCompiledModel : jest . fn ( ( ) => ( {
2629 find : ( ...args ) => {
2730 mockFind ( ...args ) ;
28- return { and : mockAnd , lean : mockQueryLean } ;
31+ return {
32+ and : mockAnd ,
33+ populate : ( ...pArgs ) => {
34+ mockPopulate ( ...pArgs ) ;
35+ return { lean : mockQueryLean } ;
36+ } ,
37+ lean : mockQueryLean
38+ } ;
2939 } ,
3040 findOne : ( ...args ) => {
3141 mockFindOne ( ...args ) ;
32- return { lean : jest . fn ( ) . mockResolvedValue ( { _id : 'doc_1' } ) } ;
42+ const chainable = {
43+ populate : ( ...pArgs ) => {
44+ mockPopulate ( ...pArgs ) ;
45+ return chainable ;
46+ } ,
47+ lean : jest . fn ( ) . mockResolvedValue ( { _id : 'doc_1' } ) ,
48+ } ;
49+ return chainable ;
3350 } ,
3451 } ) ) ,
3552 QueryEngine : mockQueryEngine ,
@@ -74,6 +91,7 @@ function makeRes() {
7491describe ( 'data.controller read RLS filters' , ( ) => {
7592 beforeEach ( ( ) => {
7693 jest . clearAllMocks ( ) ;
94+ mockEnginePopulate . mockClear ( ) ;
7795 } ) ;
7896
7997 test ( 'getAllData applies rlsFilter to find()' , async ( ) => {
@@ -101,4 +119,50 @@ describe('data.controller read RLS filters', () => {
101119 } ) ;
102120 expect ( res . json ) . toHaveBeenCalled ( ) ;
103121 } ) ;
122+
123+ test ( 'getAllData calls engine.populate() when populate param is provided' , async ( ) => {
124+ const req = makeReq ( { query : { populate : 'author,comments' } } ) ;
125+ const res = makeRes ( ) ;
126+
127+ await getAllData ( req , res ) ;
128+
129+ expect ( mockQueryEngine ) . toHaveBeenCalled ( ) ;
130+ expect ( mockEnginePopulate ) . toHaveBeenCalled ( ) ;
131+ } ) ;
132+
133+ test ( 'getAllData does not forward populate/expand to Mongo filter' , async ( ) => {
134+ const req = makeReq ( { query : { populate : 'author' , expand : 'category' , title : 'hello' } } ) ;
135+ const res = makeRes ( ) ;
136+
137+ await getAllData ( req , res ) ;
138+
139+ // mockFind is called with the raw Mongoose query (no args for Model.find())
140+ // The real filter exclusion is tested via the QueryEngine directly,
141+ // but we confirm find() was invoked and the request did not error out.
142+ expect ( mockFind ) . toHaveBeenCalledWith ( ) ;
143+ expect ( res . json ) . toHaveBeenCalled ( ) ;
144+ expect ( res . status ) . not . toHaveBeenCalledWith ( 500 ) ;
145+ } ) ;
146+
147+ test ( 'getSingleDoc calls populate on the query' , async ( ) => {
148+ const req = makeReq ( { query : { populate : 'author' } } ) ;
149+ const res = makeRes ( ) ;
150+
151+ await getSingleDoc ( req , res ) ;
152+
153+ expect ( mockPopulate ) . toHaveBeenCalledWith ( 'author' ) ;
154+ expect ( res . json ) . toHaveBeenCalled ( ) ;
155+ } ) ;
156+
157+ test ( 'getSingleDoc handles array-format populate param without crashing' , async ( ) => {
158+ const req = makeReq ( { query : { populate : [ 'author' , 'category' ] } } ) ;
159+ const res = makeRes ( ) ;
160+
161+ await getSingleDoc ( req , res ) ;
162+
163+ expect ( mockPopulate ) . toHaveBeenCalledWith ( 'author' ) ;
164+ expect ( mockPopulate ) . toHaveBeenCalledWith ( 'category' ) ;
165+ expect ( res . json ) . toHaveBeenCalled ( ) ;
166+ expect ( res . status ) . not . toHaveBeenCalledWith ( 500 ) ;
167+ } ) ;
104168} ) ;
0 commit comments