@@ -3,9 +3,12 @@ import {
33 validateToolMetadata ,
44 validateThrowsForInvalidArguments ,
55 getResponseContent ,
6+ defaultTestConfig ,
67} from "../../../helpers.js" ;
7- import { expect , it } from "vitest" ;
8+ import { describe , expect , it , vi } from "vitest" ;
89import { describeWithMongoDB , getDocsFromUntrustedContent , validateAutoConnectBehavior } from "../mongodbHelpers.js" ;
10+ import * as constants from "../../../../../src/helpers/constants.js" ;
11+ import { freshInsertDocuments } from "./find.test.js" ;
912
1013describeWithMongoDB ( "aggregate tool" , ( integration ) => {
1114 validateToolMetadata ( integration , "aggregate" , "Run an aggregation against a MongoDB collection" , [
@@ -27,7 +30,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
2730 { database : 123 , collection : "foo" , pipeline : [ ] } ,
2831 ] ) ;
2932
30- it ( "can run aggragation on non-existent database" , async ( ) => {
33+ it ( "can run aggregation on non-existent database" , async ( ) => {
3134 await integration . connectMcpClient ( ) ;
3235 const response = await integration . mcpClient ( ) . callTool ( {
3336 name : "aggregate" ,
@@ -38,7 +41,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
3841 expect ( content ) . toEqual ( "The aggregation resulted in 0 documents." ) ;
3942 } ) ;
4043
41- it ( "can run aggragation on an empty collection" , async ( ) => {
44+ it ( "can run aggregation on an empty collection" , async ( ) => {
4245 await integration . mongoClient ( ) . db ( integration . randomDbName ( ) ) . createCollection ( "people" ) ;
4346
4447 await integration . connectMcpClient ( ) ;
@@ -55,7 +58,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
5558 expect ( content ) . toEqual ( "The aggregation resulted in 0 documents." ) ;
5659 } ) ;
5760
58- it ( "can run aggragation on an existing collection" , async ( ) => {
61+ it ( "can run aggregation on an existing collection" , async ( ) => {
5962 const mongoClient = integration . mongoClient ( ) ;
6063 await mongoClient
6164 . db ( integration . randomDbName ( ) )
@@ -140,3 +143,121 @@ describeWithMongoDB("aggregate tool", (integration) => {
140143 } ;
141144 } ) ;
142145} ) ;
146+
147+ describeWithMongoDB (
148+ "aggregate tool with configured max documents per query" ,
149+ ( integration ) => {
150+ describe ( "when the aggregation results are larger than the configured limit" , ( ) => {
151+ it ( "should return documents limited to the configured limit" , async ( ) => {
152+ await freshInsertDocuments ( {
153+ collection : integration . mongoClient ( ) . db ( integration . randomDbName ( ) ) . collection ( "people" ) ,
154+ count : 1000 ,
155+ documentMapper ( index ) {
156+ return { name : `Person ${ index } ` , age : index } ;
157+ } ,
158+ } ) ;
159+ await integration . connectMcpClient ( ) ;
160+ const response = await integration . mcpClient ( ) . callTool ( {
161+ name : "aggregate" ,
162+ arguments : {
163+ database : integration . randomDbName ( ) ,
164+ collection : "people" ,
165+ pipeline : [ { $match : { age : { $gte : 10 } } } , { $sort : { name : - 1 } } ] ,
166+ } ,
167+ } ) ;
168+
169+ const content = getResponseContent ( response ) ;
170+ expect ( content ) . toContain ( "The aggregation resulted in 990 documents" ) ;
171+ expect ( content ) . toContain ( `Returning 20 documents while respecting the applied limits.` ) ;
172+ const docs = getDocsFromUntrustedContent ( content ) ;
173+ expect ( docs [ 0 ] ) . toEqual (
174+ expect . objectContaining ( {
175+ _id : expect . any ( Object ) as object ,
176+ name : "Person 999" ,
177+ age : 999 ,
178+ } )
179+ ) ;
180+ expect ( docs [ 1 ] ) . toEqual (
181+ expect . objectContaining ( {
182+ _id : expect . any ( Object ) as object ,
183+ name : "Person 998" ,
184+ age : 998 ,
185+ } )
186+ ) ;
187+ } ) ;
188+ } ) ;
189+
190+ describe ( "when counting documents exceed the configured count maxTimeMS" , ( ) => {
191+ it ( "should abort discard count operation and respond with indeterminable count" , async ( ) => {
192+ vi . spyOn ( constants , "AGG_COUNT_MAX_TIME_MS_CAP" , "get" ) . mockReturnValue ( 0.1 ) ;
193+ await freshInsertDocuments ( {
194+ collection : integration . mongoClient ( ) . db ( integration . randomDbName ( ) ) . collection ( "people" ) ,
195+ count : 1000 ,
196+ documentMapper ( index ) {
197+ return { name : `Person ${ index } ` , age : index } ;
198+ } ,
199+ } ) ;
200+ await integration . connectMcpClient ( ) ;
201+ const response = await integration . mcpClient ( ) . callTool ( {
202+ name : "aggregate" ,
203+ arguments : {
204+ database : integration . randomDbName ( ) ,
205+ collection : "people" ,
206+ pipeline : [ { $match : { age : { $gte : 10 } } } , { $sort : { name : - 1 } } ] ,
207+ } ,
208+ } ) ;
209+ const content = getResponseContent ( response ) ;
210+ expect ( content ) . toContain ( "The aggregation resulted in indeterminable number of documents" ) ;
211+ expect ( content ) . toContain ( `Returning 20 documents while respecting the applied limits.` ) ;
212+ const docs = getDocsFromUntrustedContent ( content ) ;
213+ expect ( docs [ 0 ] ) . toEqual (
214+ expect . objectContaining ( {
215+ _id : expect . any ( Object ) as object ,
216+ name : "Person 999" ,
217+ age : 999 ,
218+ } )
219+ ) ;
220+ expect ( docs [ 1 ] ) . toEqual (
221+ expect . objectContaining ( {
222+ _id : expect . any ( Object ) as object ,
223+ name : "Person 998" ,
224+ age : 998 ,
225+ } )
226+ ) ;
227+ vi . resetAllMocks ( ) ;
228+ } ) ;
229+ } ) ;
230+ } ,
231+ ( ) => ( { ...defaultTestConfig , maxDocumentsPerQuery : 20 } )
232+ ) ;
233+
234+ describeWithMongoDB (
235+ "aggregate tool with configured max bytes per query" ,
236+ ( integration ) => {
237+ describe ( "when the provided maxBytesPerQuery is hit" , ( ) => {
238+ it ( "should return only the documents that could fit in maxBytesPerQuery limit" , async ( ) => {
239+ await freshInsertDocuments ( {
240+ collection : integration . mongoClient ( ) . db ( integration . randomDbName ( ) ) . collection ( "people" ) ,
241+ count : 1000 ,
242+ documentMapper ( index ) {
243+ return { name : `Person ${ index } ` , age : index } ;
244+ } ,
245+ } ) ;
246+ await integration . connectMcpClient ( ) ;
247+ const response = await integration . mcpClient ( ) . callTool ( {
248+ name : "aggregate" ,
249+ arguments : {
250+ database : integration . randomDbName ( ) ,
251+ collection : "people" ,
252+ pipeline : [ { $match : { age : { $gte : 10 } } } , { $sort : { name : - 1 } } ] ,
253+ } ,
254+ } ) ;
255+
256+ const content = getResponseContent ( response ) ;
257+ expect ( content ) . toContain ( "The aggregation resulted in 990 documents" ) ;
258+ expect ( content ) . toContain ( `Returning 1 documents while respecting the applied limits.` ) ;
259+ } ) ;
260+ } ) ;
261+ } ,
262+ ( ) => ( { ...defaultTestConfig , maxBytesPerQuery : 100 } )
263+ ) ;
0 commit comments