Skip to content

Commit 0a1c789

Browse files
committed
chore: Add integration test for insert many
1 parent 81f9ddd commit 0a1c789

File tree

5 files changed

+307
-125
lines changed

5 files changed

+307
-125
lines changed

src/common/search/vectorSearchEmbeddings.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export class VectorSearchEmbeddings {
147147
return false;
148148
}
149149

150-
if (typeof fieldRef[0] !== "number") {
150+
if (!fieldRef.every(this.isANumber)) {
151151
return false;
152152
}
153153
}
@@ -166,4 +166,21 @@ export class VectorSearchEmbeddings {
166166
return false;
167167
}
168168
}
169+
170+
private isANumber(value: unknown): boolean {
171+
if (typeof value === "number") {
172+
return true;
173+
}
174+
175+
if (
176+
value instanceof BSON.Int32 ||
177+
value instanceof BSON.Decimal128 ||
178+
value instanceof BSON.Double ||
179+
value instanceof BSON.Long
180+
) {
181+
return true;
182+
}
183+
184+
return false;
185+
}
169186
}
Lines changed: 180 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,217 @@
1-
import { describeWithMongoDB, validateAutoConnectBehavior } from "../mongodbHelpers.js";
1+
import {
2+
createSearchIndexAndWait,
3+
createVectorSearchIndexAndWait,
4+
describeWithMongoDB,
5+
validateAutoConnectBehavior,
6+
waitUntilSearchIsReady,
7+
} from "../mongodbHelpers.js";
28

39
import {
410
getResponseContent,
511
databaseCollectionParameters,
612
validateToolMetadata,
713
validateThrowsForInvalidArguments,
814
expectDefined,
15+
getDataFromUntrustedContent,
916
} from "../../../helpers.js";
10-
import { expect, it } from "vitest";
11-
import { defaultUserConfig } from "../../../../../src/lib.js";
17+
import { beforeEach, expect, it } from "vitest";
18+
import { NodeDriverServiceProvider } from "@mongosh/service-provider-node-driver";
19+
import { afterEach } from "node:test";
20+
import { ObjectId } from "bson";
1221

13-
describeWithMongoDB(
14-
"insertMany tool",
15-
(integration) => {
16-
validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
17-
...databaseCollectionParameters,
18-
{
19-
name: "documents",
20-
type: "array",
21-
description:
22-
"The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()",
23-
required: true,
22+
describeWithMongoDB("insertMany tool when search is disabled", (integration) => {
23+
validateToolMetadata(integration, "insert-many", "Insert an array of documents into a MongoDB collection", [
24+
...databaseCollectionParameters,
25+
{
26+
name: "documents",
27+
type: "array",
28+
description:
29+
"The array of documents to insert, matching the syntax of the document argument of db.collection.insertMany()",
30+
required: true,
31+
},
32+
]);
33+
34+
validateThrowsForInvalidArguments(integration, "insert-many", [
35+
{},
36+
{ collection: "bar", database: 123, documents: [] },
37+
{ collection: [], database: "test", documents: [] },
38+
{ collection: "bar", database: "test", documents: "my-document" },
39+
{ collection: "bar", database: "test", documents: { name: "Peter" } },
40+
]);
41+
42+
const validateDocuments = async (collection: string, expectedDocuments: object[]): Promise<void> => {
43+
const collections = await integration.mongoClient().db(integration.randomDbName()).listCollections().toArray();
44+
expectDefined(collections.find((c) => c.name === collection));
45+
46+
const docs = await integration
47+
.mongoClient()
48+
.db(integration.randomDbName())
49+
.collection(collection)
50+
.find()
51+
.toArray();
52+
53+
expect(docs).toHaveLength(expectedDocuments.length);
54+
for (const expectedDocument of expectedDocuments) {
55+
expect(docs).toContainEqual(expect.objectContaining(expectedDocument));
56+
}
57+
};
58+
59+
it("creates the namespace if necessary", async () => {
60+
await integration.connectMcpClient();
61+
const response = await integration.mcpClient().callTool({
62+
name: "insert-many",
63+
arguments: {
64+
database: integration.randomDbName(),
65+
collection: "coll1",
66+
documents: [{ prop1: "value1" }],
2467
},
25-
]);
26-
27-
validateThrowsForInvalidArguments(integration, "insert-many", [
28-
{},
29-
{ collection: "bar", database: 123, documents: [] },
30-
{ collection: [], database: "test", documents: [] },
31-
{ collection: "bar", database: "test", documents: "my-document" },
32-
{ collection: "bar", database: "test", documents: { name: "Peter" } },
33-
]);
34-
35-
const validateDocuments = async (collection: string, expectedDocuments: object[]): Promise<void> => {
36-
const collections = await integration
37-
.mongoClient()
38-
.db(integration.randomDbName())
39-
.listCollections()
40-
.toArray();
41-
expectDefined(collections.find((c) => c.name === collection));
42-
43-
const docs = await integration
44-
.mongoClient()
45-
.db(integration.randomDbName())
46-
.collection(collection)
47-
.find()
48-
.toArray();
49-
50-
expect(docs).toHaveLength(expectedDocuments.length);
51-
for (const expectedDocument of expectedDocuments) {
52-
expect(docs).toContainEqual(expect.objectContaining(expectedDocument));
53-
}
68+
});
69+
70+
const content = getResponseContent(response.content);
71+
expect(content).toContain(`Inserted \`1\` document(s) into ${integration.randomDbName()}.coll1.`);
72+
73+
await validateDocuments("coll1", [{ prop1: "value1" }]);
74+
});
75+
76+
it("returns an error when inserting duplicates", async () => {
77+
const { insertedIds } = await integration
78+
.mongoClient()
79+
.db(integration.randomDbName())
80+
.collection("coll1")
81+
.insertMany([{ prop1: "value1" }]);
82+
83+
await integration.connectMcpClient();
84+
const response = await integration.mcpClient().callTool({
85+
name: "insert-many",
86+
arguments: {
87+
database: integration.randomDbName(),
88+
collection: "coll1",
89+
documents: [{ prop1: "value1", _id: { $oid: insertedIds[0] } }],
90+
},
91+
});
92+
93+
const content = getResponseContent(response.content);
94+
expect(content).toContain("Error running insert-many");
95+
expect(content).toContain("duplicate key error");
96+
expect(content).toContain(insertedIds[0]?.toString());
97+
});
98+
99+
validateAutoConnectBehavior(integration, "insert-many", () => {
100+
return {
101+
args: {
102+
database: integration.randomDbName(),
103+
collection: "coll1",
104+
documents: [{ prop1: "value1" }],
105+
},
106+
expectedResponse: `Inserted \`1\` document(s) into ${integration.randomDbName()}.coll1.`,
54107
};
108+
});
109+
});
110+
111+
describeWithMongoDB(
112+
"insertMany tool when search is enabled",
113+
(integration) => {
114+
let provider: NodeDriverServiceProvider;
55115

56-
it("creates the namespace if necessary", async () => {
116+
beforeEach(async ({ signal }) => {
57117
await integration.connectMcpClient();
118+
provider = integration.mcpServer().session.serviceProvider;
119+
await provider.createCollection(integration.randomDbName(), "test");
120+
await waitUntilSearchIsReady(provider, signal);
121+
});
122+
123+
afterEach(async () => {
124+
await provider.dropCollection(integration.randomDbName(), "test");
125+
});
126+
127+
it("inserts a document when the embedding is correct", async ({ signal }) => {
128+
await createVectorSearchIndexAndWait(
129+
provider,
130+
integration.randomDbName(),
131+
"test",
132+
[
133+
{
134+
type: "vector",
135+
path: "embedding",
136+
numDimensions: 8,
137+
similarity: "euclidean",
138+
quantization: "scalar",
139+
},
140+
],
141+
signal
142+
);
143+
58144
const response = await integration.mcpClient().callTool({
59145
name: "insert-many",
60146
arguments: {
61147
database: integration.randomDbName(),
62-
collection: "coll1",
63-
documents: [{ prop1: "value1" }],
148+
collection: "test",
149+
documents: [{ embedding: [1, 2, 3, 4, 5, 6, 7, 8] }],
64150
},
65151
});
66152

67153
const content = getResponseContent(response.content);
68-
expect(content).toContain(`Inserted \`1\` document(s) into ${integration.randomDbName()}.coll1.`);
154+
const insertedIds = extractInsertedIds(content);
155+
expect(insertedIds).toHaveLength(1);
69156

70-
await validateDocuments("coll1", [{ prop1: "value1" }]);
157+
const docCount = await provider.countDocuments(integration.randomDbName(), "test", { _id: insertedIds[0] });
158+
expect(docCount).toBe(1);
71159
});
72160

73-
it("returns an error when inserting duplicates", async () => {
74-
const { insertedIds } = await integration
75-
.mongoClient()
76-
.db(integration.randomDbName())
77-
.collection("coll1")
78-
.insertMany([{ prop1: "value1" }]);
161+
it("returns an error when there is a search index and quantisation is wrong", async ({ signal }) => {
162+
await createVectorSearchIndexAndWait(
163+
provider,
164+
integration.randomDbName(),
165+
"test",
166+
[
167+
{
168+
type: "vector",
169+
path: "embedding",
170+
numDimensions: 8,
171+
similarity: "euclidean",
172+
quantization: "scalar",
173+
},
174+
],
175+
signal
176+
);
79177

80-
await integration.connectMcpClient();
81178
const response = await integration.mcpClient().callTool({
82179
name: "insert-many",
83180
arguments: {
84181
database: integration.randomDbName(),
85-
collection: "coll1",
86-
documents: [{ prop1: "value1", _id: { $oid: insertedIds[0] } }],
182+
collection: "test",
183+
documents: [{ embedding: "oopsie" }],
87184
},
88185
});
89186

90187
const content = getResponseContent(response.content);
91-
expect(content).toContain("Error running insert-many");
92-
expect(content).toContain("duplicate key error");
93-
expect(content).toContain(insertedIds[0]?.toString());
94-
});
188+
expect(content).toContain("There were errors when inserting documents. No document was inserted.");
189+
const untrustedContent = getDataFromUntrustedContent(content);
190+
expect(untrustedContent).toContain(
191+
"- Field embedding is an embedding with 8 dimensions and scalar quantization, and the provided value is not compatible."
192+
);
95193

96-
validateAutoConnectBehavior(integration, "insert-many", () => {
97-
return {
98-
args: {
99-
database: integration.randomDbName(),
100-
collection: "coll1",
101-
documents: [{ prop1: "value1" }],
102-
},
103-
expectedResponse: `Inserted \`1\` document(s) into ${integration.randomDbName()}.coll1.`,
104-
};
194+
const oopsieCount = await provider.countDocuments(integration.randomDbName(), "test", {
195+
embedding: "oopsie",
196+
});
197+
expect(oopsieCount).toBe(0);
105198
});
106199
},
107-
() => defaultUserConfig
200+
undefined,
201+
undefined,
202+
{ search: true }
108203
);
204+
205+
function extractInsertedIds(content: string): ObjectId[] {
206+
expect(content).toContain("Documents were inserted successfully.");
207+
expect(content).toContain("Inserted IDs:");
208+
209+
const match = content.match(/Inserted IDs:\s(.*)/);
210+
const group = match?.[1];
211+
return (
212+
group
213+
?.split(",")
214+
.map((e) => e.trim())
215+
.map((e) => ObjectId.createFromHexString(e)) ?? []
216+
);
217+
}

0 commit comments

Comments
 (0)