@@ -3,9 +3,12 @@ import {
3
3
validateToolMetadata ,
4
4
validateThrowsForInvalidArguments ,
5
5
getResponseContent ,
6
+ defaultTestConfig ,
6
7
} from "../../../helpers.js" ;
7
- import { expect , it } from "vitest" ;
8
+ import { describe , expect , it , vi } from "vitest" ;
8
9
import { describeWithMongoDB , getDocsFromUntrustedContent , validateAutoConnectBehavior } from "../mongodbHelpers.js" ;
10
+ import * as constants from "../../../../../src/helpers/constants.js" ;
11
+ import { freshInsertDocuments } from "./find.test.js" ;
9
12
10
13
describeWithMongoDB ( "aggregate tool" , ( integration ) => {
11
14
validateToolMetadata ( integration , "aggregate" , "Run an aggregation against a MongoDB collection" , [
@@ -27,7 +30,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
27
30
{ database : 123 , collection : "foo" , pipeline : [ ] } ,
28
31
] ) ;
29
32
30
- it ( "can run aggragation on non-existent database" , async ( ) => {
33
+ it ( "can run aggregation on non-existent database" , async ( ) => {
31
34
await integration . connectMcpClient ( ) ;
32
35
const response = await integration . mcpClient ( ) . callTool ( {
33
36
name : "aggregate" ,
@@ -38,7 +41,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
38
41
expect ( content ) . toEqual ( "The aggregation resulted in 0 documents." ) ;
39
42
} ) ;
40
43
41
- it ( "can run aggragation on an empty collection" , async ( ) => {
44
+ it ( "can run aggregation on an empty collection" , async ( ) => {
42
45
await integration . mongoClient ( ) . db ( integration . randomDbName ( ) ) . createCollection ( "people" ) ;
43
46
44
47
await integration . connectMcpClient ( ) ;
@@ -55,7 +58,7 @@ describeWithMongoDB("aggregate tool", (integration) => {
55
58
expect ( content ) . toEqual ( "The aggregation resulted in 0 documents." ) ;
56
59
} ) ;
57
60
58
- it ( "can run aggragation on an existing collection" , async ( ) => {
61
+ it ( "can run aggregation on an existing collection" , async ( ) => {
59
62
const mongoClient = integration . mongoClient ( ) ;
60
63
await mongoClient
61
64
. db ( integration . randomDbName ( ) )
@@ -140,3 +143,121 @@ describeWithMongoDB("aggregate tool", (integration) => {
140
143
} ;
141
144
} ) ;
142
145
} ) ;
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