diff --git a/tests/cases/00_uri.ts b/tests/cases/00_uri.ts index 173bec4..5d46ecf 100644 --- a/tests/cases/00_uri.ts +++ b/tests/cases/00_uri.ts @@ -1,228 +1,242 @@ import { parse, parseSrvUrl } from "../../src/utils/uri.ts"; -import { assertEquals } from "./../test.deps.ts"; - -Deno.test({ - name: "should correctly parse mongodb://localhost", - async fn() { - const options = await parse("mongodb://localhost/"); - assertEquals(options.db, "test"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 27017); - }, -}); - -Deno.test({ - name: "should correctly parse mongodb://localhost:27017", - async fn() { - const options = await parse("mongodb://localhost:27017/"); - assertEquals(options.db, "test"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 27017); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb://localhost:27017/test?appname=hello%20world", - async fn() { - const options = await parse( - "mongodb://localhost:27017/test?appname=hello%20world", - ); - assertEquals(options.appname, "hello world"); - }, -}); - -Deno.test({ - name: "should parse ?ssl=true", - async fn() { - const options = await parse( - "mongodb://localhost:27017/test?ssl=true", - ); - assertEquals(options.tls, true); - }, -}); - -Deno.test({ - name: "should parse srv url with ?ssl=true", - async fn() { - const options = await parseSrvUrl( - "mongodb+srv://a:b@somesubdomain.somedomain.com:27017/test?ssl=true", - ); - assertEquals(options.tls, true); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb://localhost/?safe=true&readPreference=secondary", - async fn() { - const options = await parse( - "mongodb://localhost/?safe=true&readPreference=secondary", - ); - assertEquals(options.db, "test"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 27017); - }, -}); - -Deno.test({ - name: "should correctly parse mongodb://localhost:28101/", - async fn() { - const options = await parse("mongodb://localhost:28101/"); - assertEquals(options.db, "test"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 28101); - }, -}); -Deno.test({ - name: "should correctly parse mongodb://fred:foobar@localhost/baz", - async fn() { - const options = await parse("mongodb://fred:foobar@localhost/baz"); - assertEquals(options.db, "baz"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foobar"); - assertEquals(options.credential!.db, "baz"); - }, -}); - -Deno.test({ - name: "should correctly parse mongodb://fred:foo%20bar@localhost/baz", - async fn() { - const options = await parse("mongodb://fred:foo%20bar@localhost/baz"); - assertEquals(options.db, "baz"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foo bar"); - assertEquals(options.credential!.db, "baz"); - }, -}); - -Deno.test({ - name: "should correctly parse mongodb://%2Ftmp%2Fmongodb-27017.sock", - async fn() { - const options = await parse("mongodb://%2Ftmp%2Fmongodb-27017.sock"); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); - assertEquals(options.db, "test"); - }, -}); - -Deno.test({ - name: "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock", - async fn() { - const options = await parse( - "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock", - ); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foo"); - assertEquals(options.db, "test"); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb", - async fn() { - const options = await parse( - "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb", - ); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foo"); - assertEquals(options.credential!.db, "somedb"); - assertEquals(options.db, "somedb"); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb?safe=true", - async fn() { - const options = await parse( - "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb?safe=true", - ); - assertEquals(options.servers.length, 1); - assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foo"); - assertEquals(options.credential!.db, "somedb"); - assertEquals(options.db, "somedb"); - assertEquals(options.safe, true); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb://fred:foobar@localhost,server2.test:28101/baz", - async fn() { - const options = await parse( - "mongodb://fred:foobar@localhost,server2.test:28101/baz", - ); - assertEquals(options.db, "baz"); - assertEquals(options.servers.length, 2); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 27017); - assertEquals(options.servers[1].host, "server2.test"); - assertEquals(options.servers[1].port, 28101); - assertEquals(options.credential!.username, "fred"); - assertEquals(options.credential!.password, "foobar"); - assertEquals(options.credential!.db, "baz"); - }, -}); -// TODO: add more tests (https://github.com/mongodb/node-mongodb-native/blob/3.6/test/functional/url_parser.test.js) - -Deno.test({ - name: "should correctly parse uris with authSource and dbName", - async fn() { - const options = await parse( - "mongodb://a:b@localhost:27017/dbName?authSource=admin2", - ); - - assertEquals(options.db, "dbName"); - assertEquals(options.servers[0].host, "localhost"); - assertEquals(options.servers[0].port, 27017); - assertEquals(options.credential!.username, "a"); - assertEquals(options.credential!.password, "b"); - assertEquals(options.credential!.db, "admin2"); - }, -}); - -Deno.test({ - name: "should correctly parse uris with authSource and dbName", - fn() { - const options = parseSrvUrl( - "mongodb+srv://a:b@somesubdomain.somedomain.com/dbName?authSource=admin2", - ); - - assertEquals(options.db, "dbName"); - assertEquals(options.credential!.username, "a"); - assertEquals(options.credential!.password, "b"); - assertEquals(options.credential!.db, "admin2"); - }, -}); - -Deno.test({ - name: - "should correctly parse mongodb+srv://someUser:somePassword@somesubdomain.somedomain.com/someDatabaseName?retryWrites=true&w=majority", - fn() { - const options = parseSrvUrl( - "mongodb+srv://someUser:somePassword@somesubdomain.somedomain.com/someDatabaseName?retryWrites=true&w=majority", - ); - assertEquals(options.db, "someDatabaseName"); - assertEquals(options.credential?.username, "someUser"); - assertEquals(options.credential?.password, "somePassword"); - assertEquals(options.retryWrites, true); - // deno-lint-ignore no-explicit-any - assertEquals((options as any)["servers"], undefined); - }, +import { assertEquals, describe, it } from "./../test.deps.ts"; + +describe("uri", () => { + it({ + name: "should correctly parse mongodb://localhost", + async fn() { + const options = await parse("mongodb://localhost/"); + assertEquals(options.db, "test"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + }, + }); + + it({ + name: "should correctly parse mongodb://localhost", + async fn() { + const options = await parse("mongodb://localhost/"); + assertEquals(options.db, "test"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + }, + }); + + it({ + name: "should correctly parse mongodb://localhost:27017", + async fn() { + const options = await parse("mongodb://localhost:27017/"); + assertEquals(options.db, "test"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + }, + }); + + it({ + name: + "should correctly parse mongodb://localhost:27017/test?appname=hello%20world", + async fn() { + const options = await parse( + "mongodb://localhost:27017/test?appname=hello%20world", + ); + assertEquals(options.appname, "hello world"); + }, + }); + + it({ + name: "should parse ?ssl=true", + async fn() { + const options = await parse( + "mongodb://localhost:27017/test?ssl=true", + ); + assertEquals(options.tls, true); + }, + }); + + it({ + name: "should parse srv url with ?ssl=true", + async fn() { + const options = await parseSrvUrl( + "mongodb+srv://a:b@somesubdomain.somedomain.com:27017/test?ssl=true", + ); + assertEquals(options.tls, true); + }, + }); + + it({ + name: + "should correctly parse mongodb://localhost/?safe=true&readPreference=secondary", + async fn() { + const options = await parse( + "mongodb://localhost/?safe=true&readPreference=secondary", + ); + assertEquals(options.db, "test"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + }, + }); + + it({ + name: "should correctly parse mongodb://localhost:28101/", + async fn() { + const options = await parse("mongodb://localhost:28101/"); + assertEquals(options.db, "test"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 28101); + }, + }); + it({ + name: "should correctly parse mongodb://fred:foobar@localhost/baz", + async fn() { + const options = await parse("mongodb://fred:foobar@localhost/baz"); + assertEquals(options.db, "baz"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foobar"); + assertEquals(options.credential!.db, "baz"); + }, + }); + + it({ + name: "should correctly parse mongodb://fred:foo%20bar@localhost/baz", + async fn() { + const options = await parse("mongodb://fred:foo%20bar@localhost/baz"); + assertEquals(options.db, "baz"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foo bar"); + assertEquals(options.credential!.db, "baz"); + }, + }); + + it({ + name: "should correctly parse mongodb://%2Ftmp%2Fmongodb-27017.sock", + async fn() { + const options = await parse("mongodb://%2Ftmp%2Fmongodb-27017.sock"); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); + assertEquals(options.db, "test"); + }, + }); + + it({ + name: + "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock", + async fn() { + const options = await parse( + "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock", + ); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foo"); + assertEquals(options.db, "test"); + }, + }); + + it({ + name: + "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb", + async fn() { + const options = await parse( + "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb", + ); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foo"); + assertEquals(options.credential!.db, "somedb"); + assertEquals(options.db, "somedb"); + }, + }); + + it({ + name: + "should correctly parse mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb?safe=true", + async fn() { + const options = await parse( + "mongodb://fred:foo@%2Ftmp%2Fmongodb-27017.sock/somedb?safe=true", + ); + assertEquals(options.servers.length, 1); + assertEquals(options.servers[0].domainSocket, "/tmp/mongodb-27017.sock"); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foo"); + assertEquals(options.credential!.db, "somedb"); + assertEquals(options.db, "somedb"); + assertEquals(options.safe, true); + }, + }); + + it({ + name: + "should correctly parse mongodb://fred:foobar@localhost,server2.test:28101/baz", + async fn() { + const options = await parse( + "mongodb://fred:foobar@localhost,server2.test:28101/baz", + ); + assertEquals(options.db, "baz"); + assertEquals(options.servers.length, 2); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + assertEquals(options.servers[1].host, "server2.test"); + assertEquals(options.servers[1].port, 28101); + assertEquals(options.credential!.username, "fred"); + assertEquals(options.credential!.password, "foobar"); + assertEquals(options.credential!.db, "baz"); + }, + }); + // TODO: add more tests (https://github.com/mongodb/node-mongodb-native/blob/3.6/test/functional/url_parser.test.js) + + it({ + name: "should correctly parse uris with authSource and dbName", + async fn() { + const options = await parse( + "mongodb://a:b@localhost:27017/dbName?authSource=admin2", + ); + + assertEquals(options.db, "dbName"); + assertEquals(options.servers[0].host, "localhost"); + assertEquals(options.servers[0].port, 27017); + assertEquals(options.credential!.username, "a"); + assertEquals(options.credential!.password, "b"); + assertEquals(options.credential!.db, "admin2"); + }, + }); + + it({ + name: "should correctly parse uris with authSource and dbName", + fn() { + const options = parseSrvUrl( + "mongodb+srv://a:b@somesubdomain.somedomain.com/dbName?authSource=admin2", + ); + + assertEquals(options.db, "dbName"); + assertEquals(options.credential!.username, "a"); + assertEquals(options.credential!.password, "b"); + assertEquals(options.credential!.db, "admin2"); + }, + }); + + it({ + name: + "should correctly parse mongodb+srv://someUser:somePassword@somesubdomain.somedomain.com/someDatabaseName?retryWrites=true&w=majority", + fn() { + const options = parseSrvUrl( + "mongodb+srv://someUser:somePassword@somesubdomain.somedomain.com/someDatabaseName?retryWrites=true&w=majority", + ); + assertEquals(options.db, "someDatabaseName"); + assertEquals(options.credential?.username, "someUser"); + assertEquals(options.credential?.password, "somePassword"); + assertEquals(options.retryWrites, true); + // deno-lint-ignore no-explicit-any + assertEquals((options as any)["servers"], undefined); + }, + }); }); diff --git a/tests/cases/01_auth.ts b/tests/cases/01_auth.ts index 6ec970e..8065261 100644 --- a/tests/cases/01_auth.ts +++ b/tests/cases/01_auth.ts @@ -1,3 +1,4 @@ +import { Database } from "../../mod.ts"; import { cleanUsername, clientFirstMessageBare, @@ -5,156 +6,167 @@ import { passwordDigest, } from "../../src/auth/mod.ts"; import { MongoClient } from "../../src/client.ts"; -import { testWithClient } from "../common.ts"; -import { assert, assertEquals } from "../test.deps.ts"; +import { cleanTestDb, getTestDb } from "../common.ts"; +import { + afterAll, + assert, + assertEquals, + beforeAll, + describe, + it, +} from "../test.deps.ts"; -const hostname = "127.0.0.1"; +describe("auth", () => { + describe("prerequisites", () => { + it({ + name: "passwordDigest:username:password", + async fn() { + const passwordValids: { + username: string; + password: string; + digest: string; + }[] = [ + { + username: "user", + password: "pencil", + digest: "1c33006ec1ffd90f9cadcbcc0e118200", + }, + { + username: "test", + password: "test", + digest: "a6de521abefc2fed4f5876855a3484f5", + }, + ]; + for (const { username, password, digest } of passwordValids) { + const digestRes: string = await passwordDigest(username, password); + assertEquals(digestRes, digest); + } + }, + }); -interface PasswordValid { - username: string; - password: string; - digest: string; -} + it({ + name: "clientFirstMessageBare", + fn() { + const username = "1234"; + const nonce = new TextEncoder().encode("qwer"); + const result: Uint8Array = clientFirstMessageBare(username, nonce); + const expected: Uint8Array = Uint8Array.from( + [ + 110, + 61, + 49, + 50, + 51, + 52, + 44, + 114, + 61, + 99, + 88, + 100, + 108, + 99, + 103, + 61, + 61, + ], + ); + assertEquals(expected, result); + }, + }); -const passwordValids: PasswordValid[] = [ - { - username: "user", - password: "pencil", - digest: "1c33006ec1ffd90f9cadcbcc0e118200", - }, - { - username: "test", - password: "test", - digest: "a6de521abefc2fed4f5876855a3484f5", - }, -]; + it({ + name: "cleanUsername", + fn() { + const username = "first=12,last=34"; + const expected = "first=3D12=2Clast=34"; + const result = cleanUsername(username); + assertEquals(expected, result); + }, + }); -for (const { username, password, digest } of passwordValids) { - Deno.test({ - name: `passwordDigest:${username}:${password}`, - async fn() { - const digestRes: string = await passwordDigest(username, password); - assertEquals(digestRes, digest); - }, + it({ + name: "HI", + async fn() { + const salt = "rQ9ZY3MntBeuP3E1TDVC4w"; + const iter = 10000; + const data = "1c33006ec1ffd90f9cadcbcc0e118200"; + const saltedPassword = await HI( + data, + (new TextEncoder()).encode(salt), + iter, + "sha1", + ); + assertEquals( + new Uint8Array(saltedPassword), + Uint8Array.from([ + 72, + 84, + 156, + 182, + 17, + 64, + 30, + 116, + 86, + 233, + 7, + 39, + 65, + 137, + 142, + 164, + 0, + 110, + 78, + 230, + ]), + ); + }, + }); }); -} - -Deno.test({ - name: "clientFirstMessageBare", - fn() { - const username = "1234"; - const nonce = new TextEncoder().encode("qwer"); - const result: Uint8Array = clientFirstMessageBare(username, nonce); - const expected: Uint8Array = Uint8Array.from( - [ - 110, - 61, - 49, - 50, - 51, - 52, - 44, - 114, - 61, - 99, - 88, - 100, - 108, - 99, - 103, - 61, - 61, - ], - ); - assertEquals(expected, result); - }, -}); -Deno.test({ - name: "cleanUsername", - fn() { - const username = "first=12,last=34"; - const expected = "first=3D12=2Clast=34"; - const result = cleanUsername(username); - assertEquals(expected, result); - }, -}); + describe("connection", () => { + let database: Database; + let client: MongoClient; + const hostname = "127.0.0.1"; -const salt = "rQ9ZY3MntBeuP3E1TDVC4w"; -const iter = 10000; -const data = "1c33006ec1ffd90f9cadcbcc0e118200"; + beforeAll(async () => { + ({ client, database } = await getTestDb()); + await database.createUser("user1", "y3mq3mpZ3J6PGfgg"); + await database.createUser("user2", "Qa6WkQSuXF425sWZ"); + }); -Deno.test({ - name: "HI", - async fn() { - const saltedPassword = await HI( - data, - (new TextEncoder()).encode(salt), - iter, - "sha1", - ); - assertEquals( - new Uint8Array(saltedPassword), - Uint8Array.from([ - 72, - 84, - 156, - 182, - 17, - 64, - 30, - 116, - 86, - 233, - 7, - 39, - 65, - 137, - 142, - 164, - 0, - 110, - 78, - 230, - ]), - ); - }, -}); + afterAll(async () => { + await database.dropUser("user1"); + await database.dropUser("user2"); + await cleanTestDb(client, database); + }); -testWithClient("createUser", async (client) => { - const db = client.database("test"); - await db.createUser("user1", "y3mq3mpZ3J6PGfgg"); - await db.createUser("user2", "Qa6WkQSuXF425sWZ"); -}); + it("should connect with correct credentials, case 1", async () => { + const username = "user1"; + const password = "y3mq3mpZ3J6PGfgg"; + const client = new MongoClient(); + await client.connect( + `mongodb://${username}:${password}@${hostname}:27017/test`, + ); + const names = await client.listDatabases(); + assert(names instanceof Array); + assert(names.length > 0); + client.close(); + }); -Deno.test("connect authorization test 1 - test db", async () => { - const username = "user1"; - const password = "y3mq3mpZ3J6PGfgg"; - const client = new MongoClient(); - await client.connect( - `mongodb://${username}:${password}@${hostname}:27017/test`, - ); - const names = await client.listDatabases(); - assert(names instanceof Array); - assert(names.length > 0); - client.close(); -}); - -Deno.test("connect authorization test 2 - test db", async () => { - const username = "user2"; - const password = "Qa6WkQSuXF425sWZ"; - const client = new MongoClient(); - await client.connect( - `mongodb://${username}:${password}@${hostname}:27017/test`, - ); - const names = await client.listDatabases(); - assert(names instanceof Array); - assert(names.length > 0); - client.close(); -}); - -testWithClient("dropUser", async (client) => { - const db = client.database("test"); - await db.dropUser("user1"); - await db.dropUser("user2"); + it("should connect with correct credentials, case 2", async () => { + const username = "user2"; + const password = "Qa6WkQSuXF425sWZ"; + const client = new MongoClient(); + await client.connect( + `mongodb://${username}:${password}@${hostname}:27017/test`, + ); + const names = await client.listDatabases(); + assert(names instanceof Array); + assert(names.length > 0); + client.close(); + }); + }); }); diff --git a/tests/cases/02_connect.ts b/tests/cases/02_connect.ts index 15eed02..4b2161e 100644 --- a/tests/cases/02_connect.ts +++ b/tests/cases/02_connect.ts @@ -1,42 +1,55 @@ -import { assert, assertEquals } from "../test.deps.ts"; +import { + afterEach, + assert, + assertEquals, + beforeEach, + describe, + it, +} from "../test.deps.ts"; import { MongoClient } from "../../src/client.ts"; -import { testWithClient } from "../common.ts"; const hostname = "127.0.0.1"; -Deno.test("test connect", async () => { - const client = new MongoClient(); - await client.connect(`mongodb://${hostname}:27017`); - const names = await client.listDatabases(); - assert(names instanceof Array); - assert(names.length > 0); - client.close(); -}); +describe("connect", () => { + let client: MongoClient; -Deno.test("test connect With Options", async () => { - const client = new MongoClient(); - await client.connect({ - servers: [{ host: hostname, port: 27017 }], - db: "admin", + beforeEach(() => { + client = new MongoClient(); }); - const names = await client.listDatabases(); - assert(names instanceof Array); - assert(names.length > 0); - client.close(); -}); -Deno.test("test default database name from connection options", async () => { - const client = new MongoClient(); - await client.connect(`mongodb://${hostname}:27017/my-db`); - const db = client.database(); - assertEquals(db.name, "my-db"); - client.close(); -}); + afterEach(() => { + client.close(); + }); + + it("test connect", async () => { + await client.connect(`mongodb://${hostname}:27017`); + const names = await client.listDatabases(); + assert(names instanceof Array); + assert(names.length > 0); + }); + + it("test connect With Options", async () => { + await client.connect({ + servers: [{ host: hostname, port: 27017 }], + db: "admin", + }); + const names = await client.listDatabases(); + assert(names instanceof Array); + assert(names.length > 0); + }); + + it("test default database name from connection options", async () => { + await client.connect(`mongodb://${hostname}:27017/my-db`); + const db = client.database(); + assertEquals(db.name, "my-db"); + }); -testWithClient("runCommand", async (client) => { - const { databases, ok } = await client.runCommand("admin", { - listDatabases: 1, + it("runCommand", async () => { + await client.connect(`mongodb://${hostname}:27017`); + const { databases, ok } = await client.runCommand("admin", { + listDatabases: 1, + }); + assert(databases.length > 0); + assertEquals(ok, 1); }); - assert(databases.length > 0); - assertEquals(ok, 1); }); diff --git a/tests/cases/03_crud.ts b/tests/cases/03_crud.ts new file mode 100644 index 0000000..07c5e1a --- /dev/null +++ b/tests/cases/03_crud.ts @@ -0,0 +1,980 @@ +import { Database, MongoClient, ObjectId } from "../../mod.ts"; +import { + MongoInvalidArgumentError, + MongoServerError, +} from "../../src/error.ts"; +import { CreateCollectionOptions } from "../../src/types.ts"; +import { cleanTestDb, getTestDb } from "../common.ts"; +import { + afterEach, + assert, + assertEquals, + assertRejects, + beforeEach, + describe, + it, + semver, +} from "../test.deps.ts"; + +interface User { + _id: string | ObjectId; + username?: string; + password?: string; + uid?: number; + date?: Date; +} + +interface ComplexUser { + _id: string | ObjectId; + username?: string; + password?: string; + friends: string[]; + likes: { + drinks: string[]; + food: string[]; + hobbies: { + indoor: string[]; + outdoor: string[]; + }; + }; +} + +const dateNow = Date.now(); + +describe("crud operations", () => { + let client: MongoClient; + let database: Database; + const testCollectionName = "mongo_test_users"; + + beforeEach(async () => { + ({ client, database } = await getTestDb()); + }); + + afterEach(async () => { + await cleanTestDb(client, database, testCollectionName); + }); + + it("testListCollectionNames", async () => { + const users = database.collection(testCollectionName); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + const names = await database.listCollectionNames(); + assertEquals(names, [testCollectionName]); + }); + + it("testInsertOne", async () => { + const users = database.collection(testCollectionName); + const insertId = await users.insertOne({ + username: "user1", + password: "pass1", + date: new Date(dateNow), + }); + + assertEquals(insertId.toString().length, 24); + + const user1 = await users.findOne({ + _id: insertId, + }); + + assertEquals(user1, { + _id: insertId, + username: "user1", + password: "pass1", + date: new Date(dateNow), + }); + }); + + it("testUpsertOne", async () => { + const users = database.collection(testCollectionName); + await users.insertOne({ + username: "user1", + password: "pass1", + date: new Date(dateNow), + }); + const { upsertedId } = await users.updateOne( + { _id: "aaaaaaaaaaaaaaaaaaaaaaaa" }, + { + $set: { + username: "user2", + password: "pass2", + date: new Date(dateNow), + }, + }, + { upsert: true }, + ); + + assert(upsertedId); + + const user2 = await users.findOne({ + _id: upsertedId, + }); + + assertEquals(user2, { + _id: upsertedId, + username: "user2", + password: "pass2", + date: new Date(dateNow), + }); + }); + + it("testInsertOneTwice", async () => { + const users = database.collection(testCollectionName); + await users.insertOne({ + _id: "aaaaaaaaaaaaaaaaaaaaaaaa", + username: "user1", + }); + + await assertRejects( + () => + users.insertOne({ + _id: "aaaaaaaaaaaaaaaaaaaaaaaa", + username: "user1", + }), + MongoServerError, + "E11000", + ); + }); + + it("testFindOne", async () => { + const users = database.collection(testCollectionName); + await users.insertOne({ + username: "user1", + password: "pass1", + date: new Date(dateNow), + }); + const user1 = await users.findOne(); + assertEquals(Object.keys(user1!), ["_id", "username", "password", "date"]); + + const query = { username: "does not exist" }; + const findNull = await users.findOne(query); + assertEquals(findNull, undefined); + const projectionUser = await users.findOne( + {}, + { projection: { _id: 0, username: 1 } }, + ); + assertEquals(Object.keys(projectionUser!), ["username"]); + const projectionUserWithId = await users.findOne( + {}, + { projection: { username: 1 } }, + ); + assertEquals(Object.keys(projectionUserWithId!), ["_id", "username"]); + }); + + it("testFindOneWithNestedDocument", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + friends: ["Alice", "Bob"], + likes: { + drinks: ["coffee", "tea"], + food: ["pizza", "pasta"], + hobbies: { + indoor: ["puzzles", "reading"], + outdoor: ["hiking", "climbing"], + }, + }, + }); + const user1 = await users.findOne({ + "likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } }, + }); + assertEquals(Object.keys(user1!), [ + "_id", + "username", + "password", + "friends", + "likes", + ]); + }); + + it("testFindOneWithNestedDocument", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + friends: ["Alice", "Bob"], + likes: { + drinks: ["coffee", "tea"], + food: ["pizza", "pasta"], + hobbies: { + indoor: ["puzzles", "reading"], + outdoor: ["hiking", "climbing"], + }, + }, + }); + const user1 = await users.findOne({ + "likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } }, + }); + assertEquals(Object.keys(user1!), [ + "_id", + "username", + "password", + "friends", + "likes", + ]); + }); + + it("testInsertMany", async () => { + const users = database.collection("mongo_test_users"); + const { insertedCount, insertedIds } = await users.insertMany([ + { + username: "many", + password: "pass1", + }, + { + username: "many", + password: "pass2", + }, + ]); + + assertEquals(insertedCount, 2); + assertEquals(insertedIds.length, 2); + }); + + it("testFindAndModify-notfound", async () => { + const users = database.collection<{ username: string; counter: number }>( + "mongo_test_users", + ); + + const find = await users.findAndModify( + { + // this query matches no document + $and: [{ username: "a" }, { username: "b" }], + }, + { + update: { $inc: { counter: 1 } }, + new: false, + }, + ); + + assert(find === null); + assert(find !== undefined); + }); + + it("testFindAndModify-update", async () => { + const users = database.collection<{ username: string; counter: number }>( + "mongo_test_users", + ); + await users.insertOne({ username: "counter", counter: 5 }); + const updated = await users.findAndModify({ username: "counter" }, { + update: { $inc: { counter: 1 } }, + new: true, + }); + + assert(updated !== null); + assertEquals(updated.counter, 6); + assertEquals(updated.username, "counter"); + }); + + it("testFindAndModify-delete", async () => { + const users = database.collection<{ username: string; counter: number }>( + "mongo_test_users", + ); + await users.insertOne({ username: "delete", counter: 10 }); + const updated = await users.findAndModify({ username: "delete" }, { + remove: true, + }); + + assert(updated !== null); + assertEquals(updated.counter, 10); + assertEquals(updated.username, "delete"); + + const tryFind = await users.findOne({ username: "delete" }); + assertEquals(tryFind, undefined); + }); + + it("test chain call for Find", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "user3", + password: "pass3", + }, + ]); + const user = await users.find().skip(1).limit(1).toArray(); + assertEquals(user!.length > 0, true); + }); + + it("testUpdateOne", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + const result = await users.updateOne({}, { $set: { username: "USER1" } }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + it("testUpdateOneWithPush", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + likes: { + food: ["pizza", "pasta"], + drinks: [], + hobbies: { indoor: [], outdoor: [] }, + }, + friends: ["Alice", "Bob"], + }); + const result = await users.updateOne({}, { + $push: { friends: { $each: ["Carol"] } }, + }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + it("testUpdateOneWithPull", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + likes: { + food: ["pizza", "pasta"], + drinks: [], + hobbies: { indoor: [], outdoor: [] }, + }, + friends: ["Alice", "Bob"], + }); + const result = await users.updateOne({}, { + $pull: { friends: "Bob" }, + }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + it("testUpdateOneWithNestedPush", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + likes: { + food: ["pizza", "pasta"], + drinks: [], + hobbies: { indoor: [], outdoor: [] }, + }, + friends: ["Alice", "Bob"], + }); + const result = await users.updateOne({}, { + $push: { "likes.hobbies.indoor": "board games" }, + }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + it("testUpdateOneWithNestedPullAll", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + likes: { + food: ["pizza", "pasta"], + drinks: [], + hobbies: { indoor: ["board games", "cooking"], outdoor: [] }, + }, + friends: ["Alice", "Bob"], + }); + const result = await users.updateOne({}, { + $pullAll: { "likes.hobbies.indoor": ["board games", "cooking"] }, + }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + it("testUpdateOneWithNestedPull", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + likes: { + food: ["pizza", "pasta"], + drinks: [], + hobbies: { indoor: ["board games", "cooking"], outdoor: [] }, + }, + friends: ["Alice", "Bob"], + }); + const result = await users.updateOne({}, { + $pull: { "likes.hobbies.indoor": "board games" }, + }); + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + + it("testUpdateOne Error", async () => { // TODO: move tesr errors to a new file + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + try { + await users.updateOne({}, { username: "USER1" }); + assert(false); + } catch (e) { + assert(e instanceof MongoInvalidArgumentError); + } + }); + + it("testUpdateOneWithUpsert", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + const result = await users.updateOne( + { username: "user2" }, + { $set: { username: "USER2" } }, + { upsert: true }, + ); + assertEquals(result.matchedCount, 1); + assertEquals(result.modifiedCount, 0); + assertEquals(result.upsertedCount, 1); + }); + + it("testReplaceOne", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + const result = await users.replaceOne({ username: "user1" }, { + username: "user2", + }); + + assertEquals(result, { + matchedCount: 1, + modifiedCount: 1, + upsertedCount: 0, + upsertedId: undefined, + }); + }); + + it("testDeleteOne", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + username: "user1", + password: "pass1", + }); + const deleteCount = await users.deleteOne({}); + assertEquals(deleteCount, 1); + }); + + it("testFindOr", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass1", + }, + { + username: "user3", + password: "pass2", + }, + ]); + const user1 = await users + .find({ + $or: [{ password: "pass1" }, { password: "pass2" }], + }) + .toArray(); + assert(user1 instanceof Array); + assertEquals(user1.length, 3); + }); + + it("testFind", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user", + password: "pass1", + }, + { + username: "user", + password: "pass2", + }, + { + username: "user", + password: "pass3", + }, + ]); + const findUsers = await users + .find({ username: "user" }, { skip: 1, limit: 1 }) + .toArray(); + assert(findUsers instanceof Array); + assertEquals(findUsers.length, 1); + + const notFound = await users.find({ username: "does not exist" }).toArray(); + assertEquals(notFound, []); + }); + + it("test multiple queries at the same time", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + _id: "aaaaaaaaaaaaaaaaaaaaaaaa", + username: "user1", + password: "pass1", + }); + const result = await Promise.all([ + users.findOne({}, { projection: { username: 1 } }), + users.findOne({}, { projection: { username: 1 } }), + users.findOne({}, { projection: { username: 1 } }), + ]); + + assertEquals(result, [ + { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, + { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, + { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, + ]); + }); + + it("testCount", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user", + password: "pass1", + }, + { + username: "many", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + ]); + const count = await users.count({ username: "many" }); + assertEquals(count, 2); + }); + + it("testCountDocuments", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const countAll = await users.countDocuments(); + assertEquals(countAll, 4); + + const count = await users.countDocuments({ username: "many" }); + assertEquals(count, 2); + }); + + it("testEstimatedDocumentCount", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const count = await users.estimatedDocumentCount(); + assertEquals(count, 4); + }); + + it("testAggregation", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const docs = await users + .aggregate<{ _id: string; total: number }>([ + { $match: { username: "many" } }, + { $group: { _id: "$username", total: { $sum: 1 } } }, + ]) + .toArray(); + assertEquals(docs, [{ _id: "many", total: 2 }]); + }); + + it("testUpdateMany", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const result = await users.updateMany( + { username: "many" }, + { $set: { username: "MANY" } }, + ); + assertEquals(result, { + matchedCount: 2, + modifiedCount: 2, + upsertedCount: 0, + upsertedIds: undefined, + }); + }); + + it("testDeleteMany", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const deleteCount = await users.deleteMany({ username: "many" }); + assertEquals(deleteCount, 2); + }); + + it("testDistinct", async () => { + const users = database.collection("mongo_test_users"); + await users.insertMany([ + { + username: "user1", + password: "pass1", + }, + { + username: "user2", + password: "pass2", + }, + { + username: "many", + password: "pass3", + }, + { + username: "many", + password: "pass4", + }, + ]); + const user1 = await users.distinct("username"); + assertEquals(user1, ["many", "user1", "user2"]); + }); + + it("testDropConnection", async () => { + const users = database.collection("mongo_test_users"); + await users.insertOne({ + _id: "aaaaaaaaaaaaaaaaaaaaaaaa", + username: "user1", + password: "pass1", + }); + + await database.collection("mongo_test_users").drop(); + }); + + it("testFindWithSort", async () => { + const users = database.collection("mongo_test_users"); + + const condition = { uid: { $exists: true } }; + + // prepare data + for (let i = 0; i < 10; i++) { + await users.insertOne({ + username: "testFindWithSort", + password: "pass1", + uid: i, + }); + } + const all = await users.find().toArray(); + + // test sorting + const acceding = await users + .find(condition, { sort: { uid: 1 } }) + .toArray(); + const descending = await users + .find(condition, { sort: { uid: -1 } }) + .toArray(); + + assertEquals( + acceding, + all.sort((lhs, rhs) => { + return lhs.uid! - rhs.uid!; + }), + ); + assertEquals( + descending, + all.sort((lhs, rhs) => { + return -lhs.uid! - rhs.uid!; + }), + ); + + await database.collection("mongo_test_users").drop(); + }); + + it("testFindEmptyAsyncIteration", async () => { + const users = database.collection("mongo_test_users"); + for (let i = 0; i < 10; i++) { + await users.insertOne({ + username: "testFindWithSort", + password: "pass1", + uid: i, + }); + } + const cursor = users.find({ username: "does not exist" }); + const docs = []; + for await (const doc of cursor) { + docs.push(doc); + } + assertEquals(docs, []); + + await database.collection("mongo_test_users").drop(); + }); + + it("testFindWithMaxTimeMS", async () => { + const supportsMaxTimeMSInFindOne = semver.gte( + client.buildInfo!.version, + "4.2.0", + ); + + const users = database.collection("mongo_test_users"); + for (let i = 0; i < 10; i++) { + await users.insertOne({ + username: "testFindWithMaxTimeMS", + password: "pass1", + uid: i, + }); + } + const users1 = await users.find({ + uid: 0, + }, { maxTimeMS: 100 }).toArray(); + + assertEquals(users1.length, 1); + + const user1 = await users.findOne({ + uid: 0, + }, { maxTimeMS: 100 }); + + assertEquals(user1!.uid, 0); + + try { + await users.find({ + uid: 0, + $where: "sleep(10) || true", + }, { maxTimeMS: 1 }).toArray(); + assert(false); + } catch (e) { + assertEquals(e.ok, 0); + assertEquals(e.codeName, "MaxTimeMSExpired"); + assertEquals(e.errmsg, "operation exceeded time limit"); + } + + if (supportsMaxTimeMSInFindOne) { + try { + await users.findOne({ + uid: 0, + $where: "sleep(10) || true", + }, { maxTimeMS: 1 }); + assert(false); + } catch (e) { + assertEquals(e.ok, 0); + assertEquals(e.codeName, "MaxTimeMSExpired"); + assertEquals(e.errmsg, "operation exceeded time limit"); + } + } + + await database.collection("mongo_test_users").drop(); + }); + + it( + "createCollection should create a collection without options", + async () => { + const createdCollection = await database + .createCollection<{ _id: string; name: string }>( + testCollectionName, + ); + + assert(createdCollection); + + await database.collection(testCollectionName).drop(); + }, + ); + + it( + "createCollection should create a collection with options", + async () => { + interface IStudents { + _id: string; + name: string; + year: number; + major: string; + gpa?: number; + address: { + city: string; + street: string; + }; + } + + // Example from https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#mongodb-query-op.-jsonSchema + const options: CreateCollectionOptions = { + validator: { + $jsonSchema: { + bsonType: "object", + required: ["name", "year", "major", "address"], + properties: { + name: { + bsonType: "string", + description: "must be a string and is required", + }, + year: { + bsonType: "int", + minimum: 2017, + maximum: 3017, + description: + "must be an integer in [ 2017, 3017 ] and is required", + }, + major: { + enum: ["Math", "English", "Computer Science", "History", null], + description: + "can only be one of the enum values and is required", + }, + gpa: { + bsonType: ["double"], + description: "must be a double if the field exists", + }, + address: { + bsonType: "object", + required: ["city"], + properties: { + street: { + bsonType: "string", + description: "must be a string if the field exists", + }, + city: { + bsonType: "string", + "description": "must be a string and is required", + }, + }, + }, + }, + }, + }, + }; + + const createdCollection = await database + .createCollection( + testCollectionName, + options, + ); + + assert(createdCollection); + + // sanity test to check whether the speicified validator from options works + // error with message: "Document failed validation" + await assertRejects( + () => + createdCollection.insertOne({ + name: "Alice", + year: 2019, + major: "History", + gpa: 3, + address: { + city: "NYC", + street: "33rd Street", + }, + }), + ); + + // TODO: refactor to clean up the test collection properly. + // It should clean up the collection when above insertion succeeds in any case, which is unwanted result. + // Refactor when test utility is more provided. + await database.collection(testCollectionName).drop(); + }, + ); + + it( + "createCollection should throw an error with invalid options", + async () => { + const invalidOptions: CreateCollectionOptions = { + capped: true, + }; + + await assertRejects( + () => + database.createCollection<{ _id: string; name: string }>( + testCollectionName, + invalidOptions, + ), + // error with the message "the 'size' field is required when 'capped' is true" + MongoServerError, + ); + }, + ); +}); diff --git a/tests/cases/03_curd.ts b/tests/cases/03_curd.ts deleted file mode 100644 index 2c68b0e..0000000 --- a/tests/cases/03_curd.ts +++ /dev/null @@ -1,966 +0,0 @@ -import { ObjectId } from "../../mod.ts"; -import { - MongoInvalidArgumentError, - MongoServerError, -} from "../../src/error.ts"; -import { CreateCollectionOptions } from "../../src/types.ts"; -import { testWithClient, testWithTestDBClient } from "../common.ts"; -import { assert, assertEquals, assertRejects, semver } from "../test.deps.ts"; - -interface User { - _id: string | ObjectId; - username?: string; - password?: string; - uid?: number; - date?: Date; -} - -interface ComplexUser { - _id: string | ObjectId; - username?: string; - password?: string; - friends: string[]; - likes: { - drinks: string[]; - food: string[]; - hobbies: { - indoor: string[]; - outdoor: string[]; - }; - }; -} - -const dateNow = Date.now(); - -testWithClient("testListCollectionNames", async (client) => { - const db = client.database("local"); - const names = await db.listCollectionNames(); - assertEquals(names, ["startup_log"]); -}); - -testWithTestDBClient("testInsertOne", async (db) => { - const users = db.collection("mongo_test_users"); - const insertId = await users.insertOne({ - username: "user1", - password: "pass1", - date: new Date(dateNow), - }); - - assertEquals(insertId.toString().length, 24); - - const user1 = await users.findOne({ - _id: insertId, - }); - - assertEquals(user1, { - _id: insertId, - username: "user1", - password: "pass1", - date: new Date(dateNow), - }); -}); - -testWithTestDBClient("testUpsertOne", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - date: new Date(dateNow), - }); - const { upsertedId } = await users.updateOne( - { _id: "aaaaaaaaaaaaaaaaaaaaaaaa" }, - { - $set: { - username: "user2", - password: "pass2", - date: new Date(dateNow), - }, - }, - { upsert: true }, - ); - - assert(upsertedId); - - const user2 = await users.findOne({ - _id: upsertedId, - }); - - assertEquals(user2, { - _id: upsertedId, - username: "user2", - password: "pass2", - date: new Date(dateNow), - }); -}); - -testWithTestDBClient("testInsertOneTwice", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - _id: "aaaaaaaaaaaaaaaaaaaaaaaa", - username: "user1", - }); - - await assertRejects( - () => - users.insertOne({ - _id: "aaaaaaaaaaaaaaaaaaaaaaaa", - username: "user1", - }), - MongoServerError, - "E11000", - ); -}); - -testWithTestDBClient("testFindOne", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - date: new Date(dateNow), - }); - const user1 = await users.findOne(); - assertEquals(Object.keys(user1!), ["_id", "username", "password", "date"]); - - const query = { username: "does not exist" }; - const findNull = await users.findOne(query); - assertEquals(findNull, undefined); - const projectionUser = await users.findOne( - {}, - { projection: { _id: 0, username: 1 } }, - ); - assertEquals(Object.keys(projectionUser!), ["username"]); - const projectionUserWithId = await users.findOne( - {}, - { projection: { username: 1 } }, - ); - assertEquals(Object.keys(projectionUserWithId!), ["_id", "username"]); -}); - -testWithTestDBClient("testFindOneWithNestedDocument", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - friends: ["Alice", "Bob"], - likes: { - drinks: ["coffee", "tea"], - food: ["pizza", "pasta"], - hobbies: { - indoor: ["puzzles", "reading"], - outdoor: ["hiking", "climbing"], - }, - }, - }); - const user1 = await users.findOne({ - "likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } }, - }); - assertEquals(Object.keys(user1!), [ - "_id", - "username", - "password", - "friends", - "likes", - ]); -}); - -testWithTestDBClient("testFindOneWithNestedDocument", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - friends: ["Alice", "Bob"], - likes: { - drinks: ["coffee", "tea"], - food: ["pizza", "pasta"], - hobbies: { - indoor: ["puzzles", "reading"], - outdoor: ["hiking", "climbing"], - }, - }, - }); - const user1 = await users.findOne({ - "likes.hobbies.outdoor": { $elemMatch: { $eq: "climbing" } }, - }); - assertEquals(Object.keys(user1!), [ - "_id", - "username", - "password", - "friends", - "likes", - ]); -}); - -testWithTestDBClient("testInsertMany", async (db) => { - const users = db.collection("mongo_test_users"); - const { insertedCount, insertedIds } = await users.insertMany([ - { - username: "many", - password: "pass1", - }, - { - username: "many", - password: "pass2", - }, - ]); - - assertEquals(insertedCount, 2); - assertEquals(insertedIds.length, 2); -}); - -testWithTestDBClient("testFindAndModify-notfound", async (db) => { - const users = db.collection<{ username: string; counter: number }>( - "mongo_test_users", - ); - - const find = await users.findAndModify( - { - // this query matches no document - $and: [{ username: "a" }, { username: "b" }], - }, - { - update: { $inc: { counter: 1 } }, - new: false, - }, - ); - - assert(find === null); - assert(find !== undefined); -}); - -testWithTestDBClient("testFindAndModify-update", async (db) => { - const users = db.collection<{ username: string; counter: number }>( - "mongo_test_users", - ); - await users.insertOne({ username: "counter", counter: 5 }); - const updated = await users.findAndModify({ username: "counter" }, { - update: { $inc: { counter: 1 } }, - new: true, - }); - - assert(updated !== null); - assertEquals(updated.counter, 6); - assertEquals(updated.username, "counter"); -}); - -testWithTestDBClient("testFindAndModify-delete", async (db) => { - const users = db.collection<{ username: string; counter: number }>( - "mongo_test_users", - ); - await users.insertOne({ username: "delete", counter: 10 }); - const updated = await users.findAndModify({ username: "delete" }, { - remove: true, - }); - - assert(updated !== null); - assertEquals(updated.counter, 10); - assertEquals(updated.username, "delete"); - - const tryFind = await users.findOne({ username: "delete" }); - assertEquals(tryFind, undefined); -}); - -testWithTestDBClient("test chain call for Find", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "user3", - password: "pass3", - }, - ]); - const user = await users.find().skip(1).limit(1).toArray(); - assertEquals(user!.length > 0, true); -}); - -testWithTestDBClient("testUpdateOne", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - }); - const result = await users.updateOne({}, { $set: { username: "USER1" } }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); -testWithTestDBClient("testUpdateOneWithPush", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - likes: { - food: ["pizza", "pasta"], - drinks: [], - hobbies: { indoor: [], outdoor: [] }, - }, - friends: ["Alice", "Bob"], - }); - const result = await users.updateOne({}, { - $push: { friends: { $each: ["Carol"] } }, - }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); -testWithTestDBClient("testUpdateOneWithPull", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - likes: { - food: ["pizza", "pasta"], - drinks: [], - hobbies: { indoor: [], outdoor: [] }, - }, - friends: ["Alice", "Bob"], - }); - const result = await users.updateOne({}, { - $pull: { friends: "Bob" }, - }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); -testWithTestDBClient("testUpdateOneWithNestedPush", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - likes: { - food: ["pizza", "pasta"], - drinks: [], - hobbies: { indoor: [], outdoor: [] }, - }, - friends: ["Alice", "Bob"], - }); - const result = await users.updateOne({}, { - $push: { "likes.hobbies.indoor": "board games" }, - }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); -testWithTestDBClient("testUpdateOneWithNestedPullAll", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - likes: { - food: ["pizza", "pasta"], - drinks: [], - hobbies: { indoor: ["board games", "cooking"], outdoor: [] }, - }, - friends: ["Alice", "Bob"], - }); - const result = await users.updateOne({}, { - $pullAll: { "likes.hobbies.indoor": ["board games", "cooking"] }, - }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); -testWithTestDBClient("testUpdateOneWithNestedPull", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - likes: { - food: ["pizza", "pasta"], - drinks: [], - hobbies: { indoor: ["board games", "cooking"], outdoor: [] }, - }, - friends: ["Alice", "Bob"], - }); - const result = await users.updateOne({}, { - $pull: { "likes.hobbies.indoor": "board games" }, - }); - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); - -testWithTestDBClient("testUpdateOne Error", async (db) => { // TODO: move tesr errors to a new file - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - }); - try { - await users.updateOne({}, { username: "USER1" }); - assert(false); - } catch (e) { - assert(e instanceof MongoInvalidArgumentError); - } -}); - -testWithTestDBClient("testUpdateOneWithUpsert", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - }); - const result = await users.updateOne( - { username: "user2" }, - { $set: { username: "USER2" } }, - { upsert: true }, - ); - assertEquals(result.matchedCount, 1); - assertEquals(result.modifiedCount, 0); - assertEquals(result.upsertedCount, 1); -}); - -testWithTestDBClient("testReplaceOne", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - }); - const result = await users.replaceOne({ username: "user1" }, { - username: "user2", - }); - - assertEquals(result, { - matchedCount: 1, - modifiedCount: 1, - upsertedCount: 0, - upsertedId: undefined, - }); -}); - -testWithTestDBClient("testDeleteOne", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - username: "user1", - password: "pass1", - }); - const deleteCount = await users.deleteOne({}); - assertEquals(deleteCount, 1); -}); - -testWithTestDBClient("testFindOr", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass1", - }, - { - username: "user3", - password: "pass2", - }, - ]); - const user1 = await users - .find({ - $or: [{ password: "pass1" }, { password: "pass2" }], - }) - .toArray(); - assert(user1 instanceof Array); - assertEquals(user1.length, 3); -}); - -testWithTestDBClient("testFind", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user", - password: "pass1", - }, - { - username: "user", - password: "pass2", - }, - { - username: "user", - password: "pass3", - }, - ]); - const findUsers = await users - .find({ username: "user" }, { skip: 1, limit: 1 }) - .toArray(); - assert(findUsers instanceof Array); - assertEquals(findUsers.length, 1); - - const notFound = await users.find({ username: "does not exist" }).toArray(); - assertEquals(notFound, []); -}); - -testWithTestDBClient("test multiple queries at the same time", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - _id: "aaaaaaaaaaaaaaaaaaaaaaaa", - username: "user1", - password: "pass1", - }); - const result = await Promise.all([ - users.findOne({}, { projection: { username: 1 } }), - users.findOne({}, { projection: { username: 1 } }), - users.findOne({}, { projection: { username: 1 } }), - ]); - - assertEquals(result, [ - { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, - { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, - { _id: "aaaaaaaaaaaaaaaaaaaaaaaa", username: "user1" }, - ]); -}); - -testWithTestDBClient("testCount", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user", - password: "pass1", - }, - { - username: "many", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - ]); - const count = await users.count({ username: "many" }); - assertEquals(count, 2); -}); - -testWithTestDBClient("testCountDocuments", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const countAll = await users.countDocuments(); - assertEquals(countAll, 4); - - const count = await users.countDocuments({ username: "many" }); - assertEquals(count, 2); -}); - -testWithTestDBClient("testEstimatedDocumentCount", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const count = await users.estimatedDocumentCount(); - assertEquals(count, 4); -}); - -testWithTestDBClient("testAggregation", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const docs = await users - .aggregate<{ _id: string; total: number }>([ - { $match: { username: "many" } }, - { $group: { _id: "$username", total: { $sum: 1 } } }, - ]) - .toArray(); - assertEquals(docs, [{ _id: "many", total: 2 }]); -}); - -testWithTestDBClient("testUpdateMany", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const result = await users.updateMany( - { username: "many" }, - { $set: { username: "MANY" } }, - ); - assertEquals(result, { - matchedCount: 2, - modifiedCount: 2, - upsertedCount: 0, - upsertedIds: undefined, - }); -}); - -testWithTestDBClient("testDeleteMany", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const deleteCount = await users.deleteMany({ username: "many" }); - assertEquals(deleteCount, 2); -}); - -testWithTestDBClient("testDistinct", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertMany([ - { - username: "user1", - password: "pass1", - }, - { - username: "user2", - password: "pass2", - }, - { - username: "many", - password: "pass3", - }, - { - username: "many", - password: "pass4", - }, - ]); - const user1 = await users.distinct("username"); - assertEquals(user1, ["many", "user1", "user2"]); -}); - -testWithTestDBClient("testDropConnection", async (db) => { - const users = db.collection("mongo_test_users"); - await users.insertOne({ - _id: "aaaaaaaaaaaaaaaaaaaaaaaa", - username: "user1", - password: "pass1", - }); - - await db.collection("mongo_test_users").drop(); -}); - -testWithTestDBClient("testFindWithSort", async (db) => { - const users = db.collection("mongo_test_users"); - - const condition = { uid: { $exists: true } }; - - // prepare data - for (let i = 0; i < 10; i++) { - await users.insertOne({ - username: "testFindWithSort", - password: "pass1", - uid: i, - }); - } - const all = await users.find().toArray(); - - // test sorting - const acceding = await users - .find(condition, { sort: { uid: 1 } }) - .toArray(); - const descending = await users - .find(condition, { sort: { uid: -1 } }) - .toArray(); - - assertEquals( - acceding, - all.sort((lhs, rhs) => { - return lhs.uid! - rhs.uid!; - }), - ); - assertEquals( - descending, - all.sort((lhs, rhs) => { - return -lhs.uid! - rhs.uid!; - }), - ); - - await db.collection("mongo_test_users").drop(); -}); - -testWithTestDBClient("testFindEmptyAsyncIteration", async (db) => { - const users = db.collection("mongo_test_users"); - for (let i = 0; i < 10; i++) { - await users.insertOne({ - username: "testFindWithSort", - password: "pass1", - uid: i, - }); - } - const cursor = users.find({ username: "does not exist" }); - const docs = []; - for await (const doc of cursor) { - docs.push(doc); - } - assertEquals(docs, []); - - await db.collection("mongo_test_users").drop(); -}); - -testWithClient("testFindWithMaxTimeMS", async (client) => { - const db = client.database("local"); - - const supportsMaxTimeMSInFindOne = semver.gte( - client.buildInfo!.version, - "4.2.0", - ); - - const users = db.collection("mongo_test_users"); - for (let i = 0; i < 10; i++) { - await users.insertOne({ - username: "testFindWithMaxTimeMS", - password: "pass1", - uid: i, - }); - } - const users1 = await users.find({ - uid: 0, - }, { maxTimeMS: 100 }).toArray(); - - assertEquals(users1.length, 1); - - const user1 = await users.findOne({ - uid: 0, - }, { maxTimeMS: 100 }); - - assertEquals(user1!.uid, 0); - - try { - await users.find({ - uid: 0, - $where: "sleep(10) || true", - }, { maxTimeMS: 1 }).toArray(); - assert(false); - } catch (e) { - assertEquals(e.ok, 0); - assertEquals(e.codeName, "MaxTimeMSExpired"); - assertEquals(e.errmsg, "operation exceeded time limit"); - } - - if (supportsMaxTimeMSInFindOne) { - try { - await users.findOne({ - uid: 0, - $where: "sleep(10) || true", - }, { maxTimeMS: 1 }); - assert(false); - } catch (e) { - assertEquals(e.ok, 0); - assertEquals(e.codeName, "MaxTimeMSExpired"); - assertEquals(e.errmsg, "operation exceeded time limit"); - } - } - - await db.collection("mongo_test_users").drop(); -}); - -interface IStudents { - _id: string; - name: string; - year: number; - major: string; - gpa?: number; - address: { - city: string; - street: string; - }; -} - -testWithClient( - "createCollection should create a collection without options", - async (client) => { - const db = client.database("test"); - - const testCollectionName = "test_collection_for_createCollection_0"; - - const createdCollection = await db - .createCollection<{ _id: string; name: string }>( - testCollectionName, - ); - - assert(createdCollection); - - await db.collection(testCollectionName).drop(); - }, -); - -testWithClient( - "createCollection should create a collection with options", - async (client) => { - // Note that database is 'test' - const db = client.database("test"); - - const testCollectionName = "test_collection_for_createCollection_1"; - - // Example from https://www.mongodb.com/docs/manual/reference/operator/query/jsonSchema/#mongodb-query-op.-jsonSchema - const options: CreateCollectionOptions = { - validator: { - $jsonSchema: { - bsonType: "object", - required: ["name", "year", "major", "address"], - properties: { - name: { - bsonType: "string", - description: "must be a string and is required", - }, - year: { - bsonType: "int", - minimum: 2017, - maximum: 3017, - description: - "must be an integer in [ 2017, 3017 ] and is required", - }, - major: { - enum: ["Math", "English", "Computer Science", "History", null], - description: "can only be one of the enum values and is required", - }, - gpa: { - bsonType: ["double"], - description: "must be a double if the field exists", - }, - address: { - bsonType: "object", - required: ["city"], - properties: { - street: { - bsonType: "string", - description: "must be a string if the field exists", - }, - city: { - bsonType: "string", - "description": "must be a string and is required", - }, - }, - }, - }, - }, - }, - }; - - const createdCollection = await db - .createCollection( - testCollectionName, - options, - ); - - assert(createdCollection); - - // sanity test to check whether the speicified validator from options works - // error with message: "Document failed validation" - await assertRejects( - () => - createdCollection.insertOne({ - name: "Alice", - year: 2019, - major: "History", - gpa: 3, - address: { - city: "NYC", - street: "33rd Street", - }, - }), - ); - - // TODO: refactor to clean up the test collection properly. - // It should clean up the collection when above insertion succeeds in any case, which is unwanted result. - // Refactor when test utility is more provided. - await db.collection(testCollectionName).drop(); - }, -); - -testWithClient( - "createCollection should throw an error with invalid options", - async (client) => { - const db = client.database("test"); - - const testCollectionName = "test_collection_for_createCollection_2"; - const invalidOptions: CreateCollectionOptions = { - capped: true, - }; - - await assertRejects( - () => - db.createCollection<{ _id: string; name: string }>( - testCollectionName, - invalidOptions, - ), - // error with the message "the 'size' field is required when 'capped' is true" - MongoServerError, - ); - }, -); diff --git a/tests/cases/04_indexes.ts b/tests/cases/04_indexes.ts index 946c9fb..ca8b754 100644 --- a/tests/cases/04_indexes.ts +++ b/tests/cases/04_indexes.ts @@ -1,76 +1,113 @@ -import { testWithClient } from "../common.ts"; -import { assertEquals, semver } from "../test.deps.ts"; +import { Collection, Database, Document, MongoClient } from "../../mod.ts"; +import { cleanTestDb, getTestDb } from "../common.ts"; +import { + afterEach, + assertEquals, + beforeEach, + describe, + it, + semver, +} from "../test.deps.ts"; -testWithClient("createIndexes", async (client) => { - const db = client.database("test"); - const users = db.collection("mongo_test_users"); - const res = await users.createIndexes({ - indexes: [{ - name: "_name", - key: { name: 1 }, - }], - }); - assertEquals( - res, - { - createdCollectionAutomatically: true, - numIndexesBefore: 1, - numIndexesAfter: 2, - ok: 1, - }, - ); -}); +describe( + "indexes", + () => { + let client: MongoClient; + let database: Database; + let collection: Collection; + const testCollectionName = "mongo_test_users"; -testWithClient("listIndexes", async (client) => { - const db = client.database("test"); - const users = db.collection("mongo_test_users"); - const cursor = users.listIndexes(); - const indexes = await cursor.toArray(); + beforeEach(async () => { + ({ client, database } = await getTestDb()); + collection = database.collection(testCollectionName); + }); - const expected = semver.gte(client.buildInfo!.version, "4.4.0") - ? [ - { v: 2, key: { _id: 1 }, name: "_id_" }, - { v: 2, key: { name: 1 }, name: "_name" }, - ] - : [ - { v: 2, key: { _id: 1 }, name: "_id_", ns: "test.mongo_test_users" }, - { v: 2, key: { name: 1 }, name: "_name", ns: "test.mongo_test_users" }, - ]; - assertEquals( - indexes, - expected, - ); -}); + afterEach(async () => { + await cleanTestDb(client, database, testCollectionName); + }); -testWithClient("dropIndexes", async (client) => { - const db = client.database("test"); - const users = db.collection("mongo_test_users"); + it("createIndexes", async () => { + const res = await collection.createIndexes({ + indexes: [{ + name: "_name", + key: { name: 1 }, + }], + }); + assertEquals( + res, + { + createdCollectionAutomatically: true, + numIndexesBefore: 1, + numIndexesAfter: 2, + ok: 1, + }, + ); + }); - await users.createIndexes({ - indexes: [{ - name: "_name2", - key: { name: -1 }, - }], - }); + it("listIndexes", async () => { + await collection.createIndexes({ + indexes: [{ + name: "_name", + key: { name: 1 }, + }], + }); + const cursor = collection.listIndexes(); + const indexes = await cursor.toArray(); - await users.dropIndexes({ - index: "*", - }); + const expected = semver.gte(client.buildInfo!.version, "4.4.0") + ? [ + { v: 2, key: { _id: 1 }, name: "_id_" }, + { v: 2, key: { name: 1 }, name: "_name" }, + ] + : [ + { + v: 2, + key: { _id: 1 }, + name: "_id_", + ns: `test.${testCollectionName}`, + }, + { + v: 2, + key: { name: 1 }, + name: "_name", + ns: `test.${testCollectionName}`, + }, + ]; + assertEquals( + indexes, + expected, + ); + }); - const indexes = await users.listIndexes().toArray(); - const expected = semver.gte(client.buildInfo!.version, "4.4.0") - ? [ - { v: 2, key: { _id: 1 }, name: "_id_" }, - ] - : [ - { v: 2, key: { _id: 1 }, name: "_id_", ns: "test.mongo_test_users" }, - ]; - assertEquals( - indexes, - expected, - ); - assertEquals( - indexes, - expected, - ); -}); + it("dropIndexes", async () => { + await collection.createIndexes({ + indexes: [{ + name: "_name2", + key: { name: -1 }, + }], + }); + + await collection.dropIndexes({ + index: "*", + }); + + const indexes = await collection.listIndexes().toArray(); + const expected = semver.gte(client.buildInfo!.version, "4.4.0") + ? [ + { v: 2, key: { _id: 1 }, name: "_id_" }, + ] + : [ + { + v: 2, + key: { _id: 1 }, + name: "_id_", + ns: `test.${testCollectionName}`, + }, + ]; + assertEquals( + indexes, + expected, + ); + }); + }, +); diff --git a/tests/cases/05_srv.ts b/tests/cases/05_srv.ts index 3a85432..bf5abe5 100644 --- a/tests/cases/05_srv.ts +++ b/tests/cases/05_srv.ts @@ -1,4 +1,4 @@ -import { assertEquals, assertRejects } from "../test.deps.ts"; +import { assertEquals, assertRejects, describe, it } from "../test.deps.ts"; import { Srv } from "../../src/utils/srv.ts"; function mockResolver( @@ -14,136 +14,138 @@ function mockResolver( } as any; } -Deno.test({ - name: "SRV: it throws an error if url doesn't have subdomain", - fn() { - assertRejects( - () => new Srv().resolve("foo.bar"), - Error, - "Expected url in format 'host.domain.tld', received foo.bar", - ); - }, -}); +describe("SRV", () => { + it({ + name: "SRV: it throws an error if url doesn't have subdomain", + fn() { + assertRejects( + () => new Srv().resolve("foo.bar"), + Error, + "Expected url in format 'host.domain.tld', received foo.bar", + ); + }, + }); -Deno.test({ - name: - "SRV: it throws an error if SRV resolution doesn't return any SRV records", - fn() { - assertRejects( - () => new Srv(mockResolver()).resolve("mongohost.mongodomain.com"), - Error, - "Expected at least one SRV record, received 0 for url mongohost.mongodomain.com", - ); - }, -}); + it({ + name: + "SRV: it throws an error if SRV resolution doesn't return any SRV records", + fn() { + assertRejects( + () => new Srv(mockResolver()).resolve("mongohost.mongodomain.com"), + Error, + "Expected at least one SRV record, received 0 for url mongohost.mongodomain.com", + ); + }, + }); -Deno.test({ - name: "SRV: it throws an error if TXT resolution returns no records", - fn() { - assertRejects( - () => - new Srv(mockResolver([{ target: "mongohost1.mongodomain.com" }])) - .resolve("mongohost.mongodomain.com"), - Error, - "Expected exactly one TXT record, received 0 for url mongohost.mongodomain.com", - ); - }, -}); + it({ + name: "SRV: it throws an error if TXT resolution returns no records", + fn() { + assertRejects( + () => + new Srv(mockResolver([{ target: "mongohost1.mongodomain.com" }])) + .resolve("mongohost.mongodomain.com"), + Error, + "Expected exactly one TXT record, received 0 for url mongohost.mongodomain.com", + ); + }, + }); -Deno.test({ - name: - "SRV: it throws an error if TXT resolution returns more than one record", - fn() { - assertRejects( - () => - new Srv( - mockResolver( - [{ target: "mongohost1.mongodomain.com" }], - [["replicaSet=rs-0"], ["authSource=admin"]], - ), - ) - .resolve("mongohost.mongodomain.com"), - Error, - "Expected exactly one TXT record, received 2 for url mongohost.mongodomain.com", - ); - }, -}); + it({ + name: + "SRV: it throws an error if TXT resolution returns more than one record", + fn() { + assertRejects( + () => + new Srv( + mockResolver( + [{ target: "mongohost1.mongodomain.com" }], + [["replicaSet=rs-0"], ["authSource=admin"]], + ), + ) + .resolve("mongohost.mongodomain.com"), + Error, + "Expected exactly one TXT record, received 2 for url mongohost.mongodomain.com", + ); + }, + }); -Deno.test({ - name: "SRV: it throws an error if TXT record contains illegal options", - fn() { - assertRejects( - () => - new Srv( - mockResolver( - [{ target: "mongohost1.mongodomain.com" }], - [["replicaSet=rs-0&authSource=admin&ssl=true"]], - ), - ) - .resolve("mongohost.mongodomain.com"), - Error, - "Illegal uri options: ssl=true", - ); - }, -}); + it({ + name: "SRV: it throws an error if TXT record contains illegal options", + fn() { + assertRejects( + () => + new Srv( + mockResolver( + [{ target: "mongohost1.mongodomain.com" }], + [["replicaSet=rs-0&authSource=admin&ssl=true"]], + ), + ) + .resolve("mongohost.mongodomain.com"), + Error, + "Illegal uri options: ssl=true", + ); + }, + }); -Deno.test({ - name: "SRV: it correctly parses seedlist and options for valid records", - async fn() { - const result = await new Srv( - mockResolver([ - { - target: "mongohost1.mongodomain.com", - port: 27015, - }, - { - target: "mongohost2.mongodomain.com", - port: 27017, - }, - ], [["replicaSet=rs-0&authSource=admin"]]), - ).resolve("mongohost.mongodomain.com"); - assertEquals(result.servers.length, 2); - const server1 = result.servers.find( - (server) => server.host === "mongohost1.mongodomain.com", - ); - const server2 = result.servers.find((server) => - server.host === "mongohost2.mongodomain.com" - ); - assertEquals(server1!.port, 27015); - assertEquals(server2!.port, 27017); - assertEquals(result.options.replicaSet, "rs-0"); - assertEquals(result.options.authSource, "admin"); - assertEquals(result.options.loadBalanced, undefined); - }, -}); + it({ + name: "SRV: it correctly parses seedlist and options for valid records", + async fn() { + const result = await new Srv( + mockResolver([ + { + target: "mongohost1.mongodomain.com", + port: 27015, + }, + { + target: "mongohost2.mongodomain.com", + port: 27017, + }, + ], [["replicaSet=rs-0&authSource=admin"]]), + ).resolve("mongohost.mongodomain.com"); + assertEquals(result.servers.length, 2); + const server1 = result.servers.find( + (server) => server.host === "mongohost1.mongodomain.com", + ); + const server2 = result.servers.find((server) => + server.host === "mongohost2.mongodomain.com" + ); + assertEquals(server1!.port, 27015); + assertEquals(server2!.port, 27017); + assertEquals(result.options.replicaSet, "rs-0"); + assertEquals(result.options.authSource, "admin"); + assertEquals(result.options.loadBalanced, undefined); + }, + }); -Deno.test({ - name: - "SRV: it correctly parses seedlist and options for options split in two strings", - async fn() { - const result = await new Srv( - mockResolver([ - { - target: "mongohost1.mongodomain.com", - port: 27015, - }, - { - target: "mongohost2.mongodomain.com", - port: 27017, - }, - ], [["replicaS", "et=rs-0&authSource=admin"]]), - ).resolve("mongohost.mongodomain.com"); - assertEquals(result.servers.length, 2); - const server1 = result.servers.find( - (server) => server.host === "mongohost1.mongodomain.com", - ); - const server2 = result.servers.find((server) => - server.host === "mongohost2.mongodomain.com" - ); - assertEquals(server1!.port, 27015); - assertEquals(server2!.port, 27017); - assertEquals(result.options.replicaSet, "rs-0"); - assertEquals(result.options.authSource, "admin"); - assertEquals(result.options.loadBalanced, undefined); - }, + it({ + name: + "SRV: it correctly parses seedlist and options for options split in two strings", + async fn() { + const result = await new Srv( + mockResolver([ + { + target: "mongohost1.mongodomain.com", + port: 27015, + }, + { + target: "mongohost2.mongodomain.com", + port: 27017, + }, + ], [["replicaS", "et=rs-0&authSource=admin"]]), + ).resolve("mongohost.mongodomain.com"); + assertEquals(result.servers.length, 2); + const server1 = result.servers.find( + (server) => server.host === "mongohost1.mongodomain.com", + ); + const server2 = result.servers.find((server) => + server.host === "mongohost2.mongodomain.com" + ); + assertEquals(server1!.port, 27015); + assertEquals(server2!.port, 27017); + assertEquals(result.options.replicaSet, "rs-0"); + assertEquals(result.options.authSource, "admin"); + assertEquals(result.options.loadBalanced, undefined); + }, + }); }); diff --git a/tests/cases/06_gridfs.ts b/tests/cases/06_gridfs.ts index dabbb88..bcb69e5 100644 --- a/tests/cases/06_gridfs.ts +++ b/tests/cases/06_gridfs.ts @@ -1,9 +1,14 @@ -import { GridFSBucket } from "../../mod.ts"; -import { testWithClient } from "../common.ts"; +import { GridFSBucket, MongoClient } from "../../mod.ts"; +import { getClient } from "../common.ts"; import { + afterAll, + afterEach, assert, assertEquals, assertNotEquals, + beforeEach, + describe, + it, readAll, readerFromStreamReader, } from "../test.deps.ts"; @@ -14,50 +19,57 @@ async function streamReadAll(readable: ReadableStream): Promise { return result; } -testWithClient("GridFS: Echo small Hello World", async (client) => { - const bucket = new GridFSBucket(client.database("test"), { - bucketName: "echo", - }); - const upstream = await bucket.openUploadStream("test.txt"); - const writer = upstream.getWriter(); - await writer.write(new TextEncoder().encode("Hello World! 👋")); - await writer.close(); - - const getId = (await bucket.find({ filename: "test.txt" }).toArray())[0]._id; - - assert(getId); +describe("GridFS", () => { + let client: MongoClient; + const testDatabaseName = "test"; - const text = await new Response(await bucket.openDownloadStream(getId)) - .text(); - - assertEquals(text, "Hello World! 👋"); -}); + beforeEach(async () => { + client = await getClient(); + }); -testWithClient("GridFS: Echo large Image", async (client) => { - const bucket = new GridFSBucket(client.database("test"), { - bucketName: "A", + afterEach(() => { + client.close(); }); - // Set an impractically low chunkSize to test chunking algorithm - const upstream = await bucket.openUploadStream("1.jpg", { - chunkSizeBytes: 255 * 8, + afterAll(async () => { + const client = await getClient(); + const database = client.database(testDatabaseName); + + await new GridFSBucket(database, { bucketName: "deno_logo" }) + .drop().catch((e) => e); + await new GridFSBucket(database, { bucketName: "echo" }) + .drop().catch((e) => e); + await new GridFSBucket(database, { bucketName: "metadata" }) + .drop().catch((e) => e); + await new GridFSBucket(database, { bucketName: "delete" }) + .drop().catch((e) => e); + + await database.dropDatabase().catch((e) => e); + client.close(); }); - const image = await Deno.open("tests/assets/1.jpg", { read: true }); - await image.readable.pipeTo(upstream); + it("GridFS: Echo small Hello World", async () => { + const bucket = new GridFSBucket(client.database(testDatabaseName), { + bucketName: "echo", + }); + const upstream = await bucket.openUploadStream("test.txt"); + const writer = upstream.getWriter(); + await writer.write(new TextEncoder().encode("Hello World! 👋")); + await writer.close(); - const [{ _id }] = await bucket.find({ filename: "1.jpg" }).toArray(); + const getId = + (await bucket.find({ filename: "test.txt" }).toArray())[0]._id; - const expected = await Deno.readFile("tests/assets/1.jpg"); - const actual = await streamReadAll(await bucket.openDownloadStream(_id)); + assert(getId); - assertEquals(actual, expected); -}); + const text = await new Response(await bucket.openDownloadStream(getId)) + .text(); + + assertEquals(text, "Hello World! 👋"); + }); -testWithClient( - "GridFS: Echo large Image (compare with different Image)", - async (client) => { - const bucket = new GridFSBucket(client.database("test"), { + it("GridFS: Echo large Image", async () => { + const bucket = new GridFSBucket(client.database(testDatabaseName), { bucketName: "A", }); @@ -71,71 +83,97 @@ testWithClient( const [{ _id }] = await bucket.find({ filename: "1.jpg" }).toArray(); - const notExpected = await Deno.readFile("tests/assets/2.jpg"); + const expected = await Deno.readFile("tests/assets/1.jpg"); const actual = await streamReadAll(await bucket.openDownloadStream(_id)); - assertNotEquals(actual, notExpected); - }, -); + assertEquals(actual, expected); + }); -testWithClient( - "GridFS: Metadata does get stored correctly", - async (client) => { - const bucket = new GridFSBucket(client.database("test"), { - bucketName: "metadata", - }); - const upstream = await bucket.openUploadStream("metadata.txt", { - metadata: { - helloWorld: "this is a test", - }, - }); - const writer = upstream.getWriter(); - await writer.write(new TextEncoder().encode("Hello World! 👋")); - await writer.close(); + it( + "GridFS: Echo large Image (compare with different Image)", + async () => { + const bucket = new GridFSBucket(client.database(testDatabaseName), { + bucketName: "A", + }); - const file = (await bucket.find({ filename: "metadata.txt" }).toArray())[0]; + // Set an impractically low chunkSize to test chunking algorithm + const upstream = await bucket.openUploadStream("1.jpg", { + chunkSizeBytes: 255 * 8, + }); - assertEquals("this is a test", file.metadata?.helloWorld); - }, -); + const image = await Deno.open("tests/assets/1.jpg", { read: true }); + await image.readable.pipeTo(upstream); -testWithClient( - "GridFS: Delete does work as expected", - async (client) => { - const bucket = new GridFSBucket(client.database("test"), { - bucketName: "delete", - }); - const upstream = await bucket.openUploadStream("stuff.txt"); - const writer = upstream.getWriter(); - await writer.write(new TextEncoder().encode("[redacted]")); - await writer.close(); + const [{ _id }] = await bucket.find({ filename: "1.jpg" }).toArray(); - let file = await bucket.find({ filename: "stuff.txt" }).toArray(); - assert(file[0]); - await bucket.delete(file[0]._id); - file = await bucket.find({ filename: "stuff.txt" }).toArray(); - assert(!file[0]); - }, -); - -// https://www.mongodb.com/docs/manual/reference/command/createIndexes/#considerations -testWithClient( - "GridFS: Creating indexes - skip index creation on same index keys", - async (client) => { - const addAsset = async (index: number) => { - const db = client.database("test"); - const bucket = new GridFSBucket(db, { - bucketName: "sameKeys", + const notExpected = await Deno.readFile("tests/assets/2.jpg"); + const actual = await streamReadAll(await bucket.openDownloadStream(_id)); + + assertNotEquals(actual, notExpected); + }, + ); + it( + "GridFS: Metadata does get stored correctly", + async () => { + const bucket = new GridFSBucket(client.database(testDatabaseName), { + bucketName: "metadata", + }); + const upstream = await bucket.openUploadStream("metadata.txt", { + metadata: { + helloWorld: "this is a test", + }, }); - const upstream = await bucket.openUploadStream(`test-asset-${index}`); const writer = upstream.getWriter(); - await writer.write(new TextEncoder().encode(`[asset${index}]`)); + await writer.write(new TextEncoder().encode("Hello World! 👋")); await writer.close(); - return { - files: await db.collection("sameKeys.files").listIndexes().toArray(), - chunks: await db.collection("sameKeys.chunks").listIndexes().toArray(), + + const file = + (await bucket.find({ filename: "metadata.txt" }).toArray())[0]; + + assertEquals("this is a test", file.metadata?.helloWorld); + }, + ); + + it( + "GridFS: Delete does work as expected", + async () => { + const bucket = new GridFSBucket(client.database(testDatabaseName), { + bucketName: "delete", + }); + const upstream = await bucket.openUploadStream("stuff.txt"); + const writer = upstream.getWriter(); + await writer.write(new TextEncoder().encode("[redacted]")); + await writer.close(); + + let file = await bucket.find({ filename: "stuff.txt" }).toArray(); + assert(file[0]); + await bucket.delete(file[0]._id); + file = await bucket.find({ filename: "stuff.txt" }).toArray(); + assert(!file[0]); + }, + ); + + // https://www.mongodb.com/docs/manual/reference/command/createIndexes/#considerations + it( + "GridFS: Creating indexes - skip index creation on same index keys", + async () => { + const addAsset = async (index: number) => { + const database = client.database(testDatabaseName); + const bucket = new GridFSBucket(database, { + bucketName: "sameKeys", + }); + const upstream = await bucket.openUploadStream(`test-asset-${index}`); + const writer = upstream.getWriter(); + await writer.write(new TextEncoder().encode(`[asset${index}]`)); + await writer.close(); + return { + files: await database.collection("sameKeys.files").listIndexes() + .toArray(), + chunks: await database.collection("sameKeys.chunks").listIndexes() + .toArray(), + }; }; - }; - assertEquals(await addAsset(0), await addAsset(1)); - }, -); + assertEquals(await addAsset(0), await addAsset(1)); + }, + ); +}); diff --git a/tests/cases/07_worker.ts b/tests/cases/07_worker.ts index cea3f27..5913265 100644 --- a/tests/cases/07_worker.ts +++ b/tests/cases/07_worker.ts @@ -1,19 +1,21 @@ import { deferred } from "../../deps.ts"; -import { assertEquals } from "../test.deps.ts"; +import { assertEquals, describe, it } from "../test.deps.ts"; -Deno.test({ - name: "WORKER: Deno does not throw when deno_mongo is imported in worker", - fn: async () => { - const importWorker = new Worker( - import.meta.resolve("./import_worker.ts"), - { type: "module" }, - ); - const p = deferred(); - importWorker.onmessage = (e) => p.resolve(e.data); - importWorker.postMessage("startWorker"); +describe("worker", () => { + it({ + name: "WORKER: Deno does not throw when deno_mongo is imported in worker", + fn: async () => { + const importWorker = new Worker( + import.meta.resolve("./import_worker.ts"), + { type: "module" }, + ); + const p = deferred(); + importWorker.onmessage = (e) => p.resolve(e.data); + importWorker.postMessage("startWorker"); - const result = await p; - importWorker.terminate(); - assertEquals(result, "done"); - }, + const result = await p; + importWorker.terminate(); + assertEquals(result, "done"); + }, + }); }); diff --git a/tests/cases/08_find_cursor.ts b/tests/cases/08_find_cursor.ts index deb40e5..761febc 100644 --- a/tests/cases/08_find_cursor.ts +++ b/tests/cases/08_find_cursor.ts @@ -1,23 +1,25 @@ import { FindCursor } from "../../src/collection/commands/find.ts"; import { WireProtocol } from "../../src/protocol/protocol.ts"; -import { assertEquals } from "../test.deps.ts"; +import { assertEquals, describe, it } from "../test.deps.ts"; -Deno.test({ - name: - "FindCursor: Options object is immutable and not shared between cursors", - fn: () => { - const FIND_OPTIONS: { limit?: number } = {}; +describe("find cursor", () => { + it({ + name: + "FindCursor: Options object is immutable and not shared between cursors", + fn: () => { + const FIND_OPTIONS: { limit?: number } = {}; - const cursor_a = new FindCursor<{ id: number }>({ - filter: {}, - protocol: {} as WireProtocol, - collectionName: "test-collection-name", - dbName: "test-db-name", - options: FIND_OPTIONS, - }); + const cursor_a = new FindCursor<{ id: number }>({ + filter: {}, + protocol: {} as WireProtocol, + collectionName: "test-collection-name", + dbName: "test-db-name", + options: FIND_OPTIONS, + }); - cursor_a.limit(10); + cursor_a.limit(10); - assertEquals(FIND_OPTIONS.limit, undefined); - }, + assertEquals(FIND_OPTIONS.limit, undefined); + }, + }); }); diff --git a/tests/cases/09_geospatial_types.ts b/tests/cases/09_geospatial_types.ts index e1a821a..6bc1017 100644 --- a/tests/cases/09_geospatial_types.ts +++ b/tests/cases/09_geospatial_types.ts @@ -1,5 +1,5 @@ -import { Database } from "../../mod.ts"; -import { Collection } from "../../src/collection/collection.ts"; +import { type Database } from "../../mod.ts"; +import { type Collection } from "../../src/collection/collection.ts"; import { $box, $center, @@ -18,8 +18,8 @@ import { ShapeOperator, } from "../../src/types/geospatial.ts"; import { Geometry, GeometryObject, Point } from "../../src/types/geojson.ts"; -import { testWithClient } from "../common.ts"; -import { assert, assertEquals } from "../test.deps.ts"; +import { getClient } from "../common.ts"; +import { afterAll, assert, assertEquals, describe, it } from "../test.deps.ts"; interface IPlace { _id: string; @@ -89,545 +89,612 @@ const neighborhoodsData: INeighborhoods[] = geometry: item.geometry as Geometry, })); -Deno.test({ - name: "Geospatial: sanity tests for types", - fn: () => { - const geoPoint: $geoPoint = { - $geometry: { - type: "Point", - coordinates: [40, 5], - }, - }; +describe("geospatial types", () => { + const testDatabaseName = "test"; + + afterAll(async () => { + const client = await getClient(); + const database = client.database(testDatabaseName); + await database.collection("mongo_test_places").drop().catch((e) => e); + await database.collection("mongo_test_positions").drop().catch((e) => e); + await database.collection("mongo_test_neighborhoods").drop().catch((e) => + e + ); + await database.dropDatabase().catch((e) => e); + client.close(); + }); - const _geoLineString: $geoLineString = { - $geometry: { - type: "LineString", - coordinates: [[40, 5], [41, 6]], - }, - }; + it({ + name: "Geospatial: sanity tests for types", + fn: () => { + const geoPoint: $geoPoint = { + $geometry: { + type: "Point", + coordinates: [40, 5], + }, + }; - const _geoPolygon: $geoPolygon = { - $geometry: { - type: "Polygon", - coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]], - }, - }; - - const _geoMultiPoint: $geoMultiPoint = { - $geometry: { - type: "MultiPoint", - coordinates: [ - [-73.9580, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.9814, 40.7681], - ], - }, - }; - - const _geoMultiLineString: $geoMultiLineString = { - $geometry: { - type: "MultiLineString", - coordinates: [ - [[-73.96943, 40.78519], [-73.96082, 40.78095]], - [[-73.96415, 40.79229], [-73.95544, 40.78854]], - [[-73.97162, 40.78205], [-73.96374, 40.77715]], - [[-73.97880, 40.77247], [-73.97036, 40.76811]], - ], - }, - }; + const _geoLineString: $geoLineString = { + $geometry: { + type: "LineString", + coordinates: [[40, 5], [41, 6]], + }, + }; + + const _geoPolygon: $geoPolygon = { + $geometry: { + type: "Polygon", + coordinates: [[[0, 0], [3, 6], [6, 1], [0, 0]]], + }, + }; + + const _geoMultiPoint: $geoMultiPoint = { + $geometry: { + type: "MultiPoint", + coordinates: [ + [-73.9580, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + ], + }, + }; - const _geoMultiPolygon: $geoMultiPolygon = { - $geometry: { - type: "MultiPolygon", - coordinates: [ - [ + const _geoMultiLineString: $geoMultiLineString = { + $geometry: { + type: "MultiLineString", + coordinates: [ + [[-73.96943, 40.78519], [-73.96082, 40.78095]], + [[-73.96415, 40.79229], [-73.95544, 40.78854]], + [[-73.97162, 40.78205], [-73.96374, 40.77715]], + [[-73.97880, 40.77247], [-73.97036, 40.76811]], + ], + }, + }; + + const _geoMultiPolygon: $geoMultiPolygon = { + $geometry: { + type: "MultiPolygon", + coordinates: [ [ - [-73.958, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.9814, 40.7681], - [-73.958, 40.8003], + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + [-73.958, 40.8003], + ], ], - ], - [ [ - [-73.958, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.958, 40.8003], + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.958, 40.8003], + ], ], ], - ], - }, - }; + }, + }; - const _geoCollection: $geoCollection = { - $geometry: { - type: "GeometryCollection", - geometries: [ - { - type: "MultiPoint", - coordinates: [ - [-73.9580, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.9814, 40.7681], - ], - }, - { - type: "MultiLineString", - coordinates: [ - [[-73.96943, 40.78519], [-73.96082, 40.78095]], - [[-73.96415, 40.79229], [-73.95544, 40.78854]], - [[-73.97162, 40.78205], [-73.96374, 40.77715]], - [[-73.97880, 40.77247], [-73.97036, 40.76811]], - ], - }, - ], - }, - }; - - const box: $box = { $box: [[0, 0], [100, 100]] }; - const polygon: $polygon = { $polygon: [[0, 0], [3, 6], [6, 0]] }; - const center: $center = { $center: [[-74, 40.74], 10] }; - const centerSphere: $centerSphere = { - $centerSphere: [[-88, 30], 10 / 3963.2], - }; - - // union type tests - const _shapeOperator1: ShapeOperator = box; - const _shapeOperator2: ShapeOperator = polygon; - const _shapeOperator3: ShapeOperator = center; - const _shapeOperator4: ShapeOperator = centerSphere; - - const _centerSpecifier1: CenterSpecifier = geoPoint; - const _centerSpecifier2: CenterSpecifier = { ...geoPoint, $minDistance: 1 }; - const _centerSpecifier3: CenterSpecifier = { ...geoPoint, $maxDistance: 1 }; - const _centerSpecifier4: CenterSpecifier = { - ...geoPoint, - $minDistance: 1, - $maxDistance: 1, - }; - const _legacyPoint: CenterSpecifier = [0, 1]; - const _documentStylePoint: CenterSpecifier = { lon: 0, lat: 1 }; - }, -}); + const _geoCollection: $geoCollection = { + $geometry: { + type: "GeometryCollection", + geometries: [ + { + type: "MultiPoint", + coordinates: [ + [-73.9580, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + ], + }, + { + type: "MultiLineString", + coordinates: [ + [[-73.96943, 40.78519], [-73.96082, 40.78095]], + [[-73.96415, 40.79229], [-73.95544, 40.78854]], + [[-73.97162, 40.78205], [-73.96374, 40.77715]], + [[-73.97880, 40.77247], [-73.97036, 40.76811]], + ], + }, + ], + }, + }; -/** - * Sanity tests for geospatial queries. - * - * Tests are based on the summary table of geospatial query operators from the link below. - * https://www.mongodb.com/docs/manual/geospatial-queries/#geospatial-models - * - * Test data picked from below links - * https://www.mongodb.com/docs/manual/tutorial/geospatial-tutorial/#searching-for-restaurants - * - * Places - * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json - * - * Neighborhoods - * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json - */ -testWithClient( - "Geospatial: sanity tests for types by actual querying", - async (client) => { - const db = client.database("test"); - await test_$near_and_$nearSphere_queries(db); - await test_$geoWithin_queries(db); - await test_$geoIntersects(db); - await db.collection("mongo_test_places").drop().catch(console.error); - await db.collection("mongo_test_positions").drop().catch(console.error); - await db.collection("mongo_test_neighborhoods").drop().catch(console.error); - }, -); + const box: $box = { $box: [[0, 0], [100, 100]] }; + const polygon: $polygon = { $polygon: [[0, 0], [3, 6], [6, 0]] }; + const center: $center = { $center: [[-74, 40.74], 10] }; + const centerSphere: $centerSphere = { + $centerSphere: [[-88, 30], 10 / 3963.2], + }; -async function test_$near_and_$nearSphere_queries(db: Database) { - const placeCollection = db.collection("mongo_test_places"); + // union type tests + const _shapeOperator1: ShapeOperator = box; + const _shapeOperator2: ShapeOperator = polygon; + const _shapeOperator3: ShapeOperator = center; + const _shapeOperator4: ShapeOperator = centerSphere; - await placeCollection.createIndexes({ - indexes: [ - // An 2dsphere index for `location` - { - name: "location_2dsphere", - key: { location: "2dsphere" }, - "2dsphereIndexVersion": 3, - }, - // An 2d index for `legacyLocation` - { - name: "legacyLocation_2d", - key: { legacyLocation: "2d" }, - }, - { - name: "legacyLocationDocument_2d", - key: { legacyLocationDocument: "2d" }, - }, - ], + const _centerSpecifier1: CenterSpecifier = geoPoint; + const _centerSpecifier2: CenterSpecifier = { + ...geoPoint, + $minDistance: 1, + }; + const _centerSpecifier3: CenterSpecifier = { + ...geoPoint, + $maxDistance: 1, + }; + const _centerSpecifier4: CenterSpecifier = { + ...geoPoint, + $minDistance: 1, + $maxDistance: 1, + }; + const _legacyPoint: CenterSpecifier = [0, 1]; + const _documentStylePoint: CenterSpecifier = { lon: 0, lat: 1 }; + }, }); - await placeCollection.insertMany(placeData); - - const queries = [ - { - coordinates: [-73.856077, 40.848447], - }, - { - // with a $maxDistance contraint - coordinates: [-73.856077, 40.848447], - $maxDistance: 100, - }, - { - // with a $minDistance contraint - coordinates: [-73.856077, 40.848447], - $minDistance: 100, - }, - { - // GeoJSON with a $min/$max distance contraint - coordinates: [-73.856077, 40.848447], - $maxDistance: 100, - $minDistance: 10, + /** + * Sanity tests for geospatial queries. + * + * Tests are based on the summary table of geospatial query operators from the link below. + * https://www.mongodb.com/docs/manual/geospatial-queries/#geospatial-models + * + * Test data picked from below links + * https://www.mongodb.com/docs/manual/tutorial/geospatial-tutorial/#searching-for-restaurants + * + * Places + * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/restaurants.json + * + * Neighborhoods + * https://raw.githubusercontent.com/mongodb/docs-assets/geospatial/neighborhoods.json + */ + it( + "Geospatial: sanity tests for types by actual querying", + async () => { + const client = await getClient(); + const database = client.database(testDatabaseName); + await test_$near_and_$nearSphere_queries(database); + await test_$geoWithin_queries(database); + await test_$geoIntersects(database); + await database.collection("mongo_test_places").drop().catch( + console.error, + ); + await database.collection("mongo_test_positions").drop().catch( + console.error, + ); + await database.collection("mongo_test_neighborhoods").drop().catch( + console.error, + ); + client.close(); }, - ]; + ); - await testGeoJsonQueries(placeCollection, queries); - await testLegacyQueries(placeCollection, queries); -} + async function test_$near_and_$nearSphere_queries(database: Database) { + const placeCollection = database.collection("mongo_test_places"); -async function testGeoJsonQueries( - placeCollection: Collection, - queries: ({ coordinates: number[] } & DistanceConstraint)[], -) { - const geoJsonQueries: ($geoPoint & DistanceConstraint)[] = queries.map( - (data) => { - const { coordinates, $maxDistance, $minDistance } = data; - const geoJsonQueryItem: $geoPoint & DistanceConstraint = { - $geometry: { - type: "Point", - coordinates, + await placeCollection.createIndexes({ + indexes: [ + // An 2dsphere index for `location` + { + name: "location_2dsphere", + key: { location: "2dsphere" }, + "2dsphereIndexVersion": 3, }, - $minDistance, - $maxDistance, - }; + // An 2d index for `legacyLocation` + { + name: "legacyLocation_2d", + key: { legacyLocation: "2d" }, + }, + { + name: "legacyLocationDocument_2d", + key: { legacyLocationDocument: "2d" }, + }, + ], + }); - return removeUndefinedDistanceConstraint(geoJsonQueryItem); - }, - ); + await placeCollection.insertMany(placeData); - for await (const geoQuery of geoJsonQueries) { - // with $near - await placeCollection.find({ - location: { - $near: geoQuery, + const queries = [ + { + coordinates: [-73.856077, 40.848447], }, - }).toArray(); - - // with $nearSphere - await placeCollection.find({ - location: { - $nearSphere: geoQuery, + { + // with a $maxDistance contraint + coordinates: [-73.856077, 40.848447], + $maxDistance: 100, }, - }).toArray(); + { + // with a $minDistance contraint + coordinates: [-73.856077, 40.848447], + $minDistance: 100, + }, + { + // GeoJSON with a $min/$max distance contraint + coordinates: [-73.856077, 40.848447], + $maxDistance: 100, + $minDistance: 10, + }, + ]; + + await testGeoJsonQueries(placeCollection, queries); + await testLegacyQueries(placeCollection, queries); } -} -async function testLegacyQueries( - placeCollection: Collection, - queries: ({ coordinates: number[] } & DistanceConstraint)[], -) { - const legacyQueries: - ({ $near: LegacyPoint; $nearSphere: LegacyPoint } & DistanceConstraint)[] = - queries.map( - (data) => { - const { coordinates, $maxDistance, $minDistance } = data; - - const queryItem = { - $near: coordinates, - $nearSphere: coordinates, - $minDistance, - $maxDistance, - }; - - if ($maxDistance === undefined) { - delete queryItem["$maxDistance"]; - } - if ($minDistance === undefined) { - delete queryItem["$minDistance"]; - } - - return queryItem; + async function testGeoJsonQueries( + placeCollection: Collection, + queries: ({ coordinates: number[] } & DistanceConstraint)[], + ) { + const geoJsonQueries: ($geoPoint & DistanceConstraint)[] = queries.map( + (data) => { + const { coordinates, $maxDistance, $minDistance } = data; + const geoJsonQueryItem: $geoPoint & DistanceConstraint = { + $geometry: { + type: "Point", + coordinates, + }, + $minDistance, + $maxDistance, + }; + + return removeUndefinedDistanceConstraint(geoJsonQueryItem); + }, + ); + + for await (const geoQuery of geoJsonQueries) { + // with $near + await placeCollection.find({ + location: { + $near: geoQuery, }, - ); + }).toArray(); - for await (const query of legacyQueries) { - // with $near - await placeCollection.find({ - legacyLocation: query as LegacyNearQuery, - }).toArray(); + // with $nearSphere + await placeCollection.find({ + location: { + $nearSphere: geoQuery, + }, + }).toArray(); + } + } - // with $nearSphere - await placeCollection.find({ - legacyLocation: query as LegacyNearSphereQuery, - }).toArray(); + async function testLegacyQueries( + placeCollection: Collection, + queries: ({ coordinates: number[] } & DistanceConstraint)[], + ) { + const legacyQueries: ( + & { $near: LegacyPoint; $nearSphere: LegacyPoint } + & DistanceConstraint + )[] = queries.map( + (data) => { + const { coordinates, $maxDistance, $minDistance } = data; + + const queryItem = { + $near: coordinates, + $nearSphere: coordinates, + $minDistance, + $maxDistance, + }; + + if ($maxDistance === undefined) { + delete queryItem["$maxDistance"]; + } + if ($minDistance === undefined) { + delete queryItem["$minDistance"]; + } + + return queryItem; + }, + ); - const [lon, lat] = query.$near!; - const { $minDistance, $maxDistance } = query; + for await (const query of legacyQueries) { + // with $near + await placeCollection.find({ + legacyLocation: query as LegacyNearQuery, + }).toArray(); - const documentStyleQuery = removeUndefinedDistanceConstraint({ - $near: { lon, lat }, - $nearSphere: { lon, lat }, - $minDistance, - $maxDistance, - }); + // with $nearSphere + await placeCollection.find({ + legacyLocation: query as LegacyNearSphereQuery, + }).toArray(); - // with $near - await placeCollection.find({ - legacyLocationDocument: documentStyleQuery as LegacyNearDocumentQuery, - }).toArray(); + const [lon, lat] = query.$near!; + const { $minDistance, $maxDistance } = query; - // with $nearSphere - await placeCollection.find({ - legacyLocationDocument: - documentStyleQuery as LegacyNearSphereDocumentQuery, - }).toArray(); + const documentStyleQuery = removeUndefinedDistanceConstraint({ + $near: { lon, lat }, + $nearSphere: { lon, lat }, + $minDistance, + $maxDistance, + }); + + // with $near + await placeCollection.find({ + legacyLocationDocument: documentStyleQuery as LegacyNearDocumentQuery, + }).toArray(); + + // with $nearSphere + await placeCollection.find({ + legacyLocationDocument: + documentStyleQuery as LegacyNearSphereDocumentQuery, + }).toArray(); + } } -} -function removeUndefinedDistanceConstraint( - obj: T & DistanceConstraint, -): T & DistanceConstraint { - const result = { ...obj }; - const { $minDistance, $maxDistance } = obj; + function removeUndefinedDistanceConstraint( + obj: T & DistanceConstraint, + ): T & DistanceConstraint { + const result = { ...obj }; + const { $minDistance, $maxDistance } = obj; - if ($minDistance === undefined) { - delete result["$minDistance"]; - } + if ($minDistance === undefined) { + delete result["$minDistance"]; + } - if ($maxDistance === undefined) { - delete result["$maxDistance"]; - } + if ($maxDistance === undefined) { + delete result["$maxDistance"]; + } - return result; -} + return result; + } -async function test_$geoWithin_queries(db: Database) { - await test_$geoWithin_by_GeoJson_queries(db); - await test_$geoWithin_by_ShapeOperators(db); -} + async function test_$geoWithin_queries(database: Database) { + await test_$geoWithin_by_GeoJson_queries(database); + await test_$geoWithin_by_ShapeOperators(database); + } -async function test_$geoWithin_by_GeoJson_queries(db: Database) { - const places = db.collection("mongo_test_places"); + async function test_$geoWithin_by_GeoJson_queries(database: Database) { + const places = database.collection("mongo_test_places"); - const foundPlacesByPolygon = await places.find({ - location: { - $geoWithin: { - $geometry: { - type: "Polygon", - coordinates: [ - [ - [-73.857, 40.848], - [-73.857, 40.849], - [-73.856, 40.849], - [-73.856, 40.848], - [-73.857, 40.848], + const foundPlacesByPolygon = await places.find({ + location: { + $geoWithin: { + $geometry: { + type: "Polygon", + coordinates: [ + [ + [-73.857, 40.848], + [-73.857, 40.849], + [-73.856, 40.849], + [-73.856, 40.848], + [-73.857, 40.848], + ], ], - ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPlacesByPolygon); + assert(foundPlacesByPolygon); - // Manipulated the query so that there should be only one place, which is "Morris Park Bake Shop" - assertEquals(foundPlacesByPolygon.length, 1); - assertEquals(foundPlacesByPolygon[0].name, "Morris Park Bake Shop"); + // Manipulated the query so that there should be only one place, which is "Morris Park Bake Shop" + assertEquals(foundPlacesByPolygon.length, 1); + assertEquals(foundPlacesByPolygon[0].name, "Morris Park Bake Shop"); - const foundPlacesByMultiPolygon = await places.find({ - location: { - $geoWithin: { - $geometry: { - type: "MultiPolygon", - coordinates: [ - [ + const foundPlacesByMultiPolygon = await places.find({ + location: { + $geoWithin: { + $geometry: { + type: "MultiPolygon", + coordinates: [ [ - [-73.958, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.9814, 40.7681], - [-73.958, 40.8003], + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.9814, 40.7681], + [-73.958, 40.8003], + ], ], - ], - [ [ - [-73.958, 40.8003], - [-73.9498, 40.7968], - [-73.9737, 40.7648], - [-73.958, 40.8003], + [ + [-73.958, 40.8003], + [-73.9498, 40.7968], + [-73.9737, 40.7648], + [-73.958, 40.8003], + ], ], ], - ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPlacesByMultiPolygon); + assert(foundPlacesByMultiPolygon); - // Manipulated the places data so that there should be only one place, which is "Cafe1 & Cafe 4 (American Museum Of Natural History)" - assertEquals(foundPlacesByMultiPolygon.length, 1); - assertEquals( - foundPlacesByMultiPolygon[0].name, - "Cafe1 & Cafe 4 (American Museum Of Natural History)", - ); -} + // Manipulated the places data so that there should be only one place, which is "Cafe1 & Cafe 4 (American Museum Of Natural History)" + assertEquals(foundPlacesByMultiPolygon.length, 1); + assertEquals( + foundPlacesByMultiPolygon[0].name, + "Cafe1 & Cafe 4 (American Museum Of Natural History)", + ); + } -async function test_$geoWithin_by_ShapeOperators(db: Database) { - const positions = db.collection("mongo_test_positions"); + async function test_$geoWithin_by_ShapeOperators(database: Database) { + const positions = database.collection("mongo_test_positions"); - await positions.createIndexes({ - indexes: [ - // An 2d index for `pos` - { - name: "pos_2d", - key: { pos: "2d" }, - }, - ], - }); + await positions.createIndexes({ + indexes: [ + // An 2d index for `pos` + { + name: "pos_2d", + key: { pos: "2d" }, + }, + ], + }); - const dataToInsert: Omit[] = []; - const xs = [-1, 0, 1]; - const ys = [-1, 0, 1]; + const dataToInsert: Omit[] = []; + const xs = [-1, 0, 1]; + const ys = [-1, 0, 1]; - for (const x of xs) { - for (const y of ys) { - dataToInsert.push({ pos: [x, y] }); + for (const x of xs) { + for (const y of ys) { + dataToInsert.push({ pos: [x, y] }); + } } - } - await positions.insertMany(dataToInsert); + await positions.insertMany(dataToInsert); - await test_$geoWithin_by_$box(positions); - await test_$geoWithin_by_$polygon(positions); - await test_$geoWithin_by_$center(positions); - await test_$geoWithin_by_$centerSphere(positions); -} + await test_$geoWithin_by_$box(positions); + await test_$geoWithin_by_$polygon(positions); + await test_$geoWithin_by_$center(positions); + await test_$geoWithin_by_$centerSphere(positions); + } -async function test_$geoWithin_by_$box(positions: Collection) { - const foundPositions = await positions.find({ - pos: { - $geoWithin: { - $box: [ - [-1, -1], // bottom left - [1, 1], // upper right - ], + async function test_$geoWithin_by_$box(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $box: [ + [-1, -1], // bottom left + [1, 1], // upper right + ], + }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPositions); - assertEquals(foundPositions.length, 9); -} + assert(foundPositions); + assertEquals(foundPositions.length, 9); + } -async function test_$geoWithin_by_$polygon(positions: Collection) { - const foundPositions = await positions.find({ - pos: { - $geoWithin: { - $polygon: [[-1, 0], [0, 1], [1, 0], [0, -1]], // a diamond shaped polygon + async function test_$geoWithin_by_$polygon(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $polygon: [[-1, 0], [0, 1], [1, 0], [0, -1]], // a diamond shaped polygon + }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPositions); - assertEquals(foundPositions.length, 5); -} + assert(foundPositions); + assertEquals(foundPositions.length, 5); + } -async function test_$geoWithin_by_$center(positions: Collection) { - const foundPositions = await positions.find({ - pos: { - $geoWithin: { - $center: [[0, 0], 1], // a circle with radius 1 + async function test_$geoWithin_by_$center(positions: Collection) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $center: [[0, 0], 1], // a circle with radius 1 + }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPositions); - assertEquals(foundPositions.length, 5); -} + assert(foundPositions); + assertEquals(foundPositions.length, 5); + } -async function test_$geoWithin_by_$centerSphere( - positions: Collection, -) { - const foundPositions = await positions.find({ - pos: { - $geoWithin: { - $centerSphere: [[0, 0], 0.0174535], // a sphere with 0.0174535 radian + async function test_$geoWithin_by_$centerSphere( + positions: Collection, + ) { + const foundPositions = await positions.find({ + pos: { + $geoWithin: { + $centerSphere: [[0, 0], 0.0174535], // a sphere with 0.0174535 radian + }, }, - }, - }).toArray(); + }).toArray(); - assert(foundPositions); - // 0.0174535 radian is a bit greater than 1.0, so it covers 5 points in the coordinates - assertEquals(foundPositions.length, 5); -} + assert(foundPositions); + // 0.0174535 radian is a bit greater than 1.0, so it covers 5 points in the coordinates + assertEquals(foundPositions.length, 5); + } -async function test_$geoIntersects(db: Database) { - const neighborhoods = db.collection( - "mongo_test_neighborhoods", - ); + async function test_$geoIntersects(database: Database) { + const neighborhoods = database.collection( + "mongo_test_neighborhoods", + ); + + await neighborhoods.createIndexes({ + indexes: [ + // An 2dsphere index for `geometry` + { + name: "geometry_2dsphere", + key: { geometry: "2dsphere" }, + "2dsphereIndexVersion": 3, + }, + ], + }); - await neighborhoods.createIndexes({ - indexes: [ - // An 2dsphere index for `geometry` - { - name: "geometry_2dsphere", - key: { geometry: "2dsphere" }, - "2dsphereIndexVersion": 3, + await neighborhoods.insertMany(neighborhoodsData); + + const intersectionByPoint = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + "type": "Point", + "coordinates": [-73.95095412329623, 40.77543392621753], + }, + }, }, - ], - }); + }).toArray(); - await neighborhoods.insertMany(neighborhoodsData); + assert(intersectionByPoint); + assertEquals(intersectionByPoint.length, 1); + assertEquals(intersectionByPoint[0].name, "Yorkville"); - const intersectionByPoint = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - "type": "Point", - "coordinates": [-73.95095412329623, 40.77543392621753], + const intersectionByLineString = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "LineString", + coordinates: [ + [-73.95852104926365, 40.77889702821282], + [-73.95095412329623, 40.77543392621753], + ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(intersectionByPoint); - assertEquals(intersectionByPoint.length, 1); - assertEquals(intersectionByPoint[0].name, "Yorkville"); + assert(intersectionByLineString); + assertEquals(intersectionByLineString.length, 1); + assertEquals(intersectionByLineString[0].name, "Yorkville"); - const intersectionByLineString = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - type: "LineString", - coordinates: [ - [-73.95852104926365, 40.77889702821282], - [-73.95095412329623, 40.77543392621753], - ], + const intersectionByPolygon = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "Polygon", + coordinates: [ + [ + [ + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + [ + -73.95296019452276, + 40.779724262361626, + ], + [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95852104926365, + 40.77889702821282, + ], + ], + ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(intersectionByLineString); - assertEquals(intersectionByLineString.length, 1); - assertEquals(intersectionByLineString[0].name, "Yorkville"); + assert(intersectionByPolygon); + assertEquals(intersectionByPolygon.length, 1); + assertEquals(intersectionByPolygon[0].name, "Yorkville"); - const intersectionByPolygon = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - type: "Polygon", - coordinates: [ - [ + const intersectionByMultiPoint = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "MultiPoint", + coordinates: [ [ -73.95852104926365, 40.77889702821282, @@ -649,115 +716,21 @@ async function test_$geoIntersects(db: Database) { 40.77889702821282, ], ], - ], - }, - }, - }, - }).toArray(); - - assert(intersectionByPolygon); - assertEquals(intersectionByPolygon.length, 1); - assertEquals(intersectionByPolygon[0].name, "Yorkville"); - - const intersectionByMultiPoint = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - type: "MultiPoint", - coordinates: [ - [ - -73.95852104926365, - 40.77889702821282, - ], - [ - -73.95095412329623, - 40.77543392621753, - ], - [ - -73.95296019452276, - 40.779724262361626, - ], - [ - -73.95605545882601, - 40.77954043344108, - ], - [ - -73.95852104926365, - 40.77889702821282, - ], - ], - }, - }, - }, - }).toArray(); - - assert(intersectionByMultiPoint); - assertEquals(intersectionByMultiPoint.length, 1); - assertEquals(intersectionByMultiPoint[0].name, "Yorkville"); - - const intersectionByMultiLineString = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - type: "MultiLineString", - coordinates: [ - [ - [ - -73.95852104926365, - 40.77889702821282, - ], - [ - -73.95095412329623, - 40.77543392621753, - ], - ], - [ - [ - -73.95605545882601, - 40.77954043344108, - ], - [ - -73.95296019452276, - 40.779724262361626, - ], - ], - ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(intersectionByMultiLineString); - assertEquals(intersectionByMultiLineString.length, 1); - assertEquals(intersectionByMultiLineString[0].name, "Yorkville"); + assert(intersectionByMultiPoint); + assertEquals(intersectionByMultiPoint.length, 1); + assertEquals(intersectionByMultiPoint[0].name, "Yorkville"); - const intersectionByMultiPolygon = await neighborhoods.find({ - geometry: { - $geoIntersects: { - $geometry: { - type: "MultiPolygon", - coordinates: [ - [ - [ - [ - -73.958, - 40.8003, - ], - [ - -73.9737, - 40.7648, - ], - [ - -73.9498, - 40.7968, - ], - [ - -73.958, - 40.8003, - ], - ], - ], - [ + const intersectionByMultiLineString = await neighborhoods.find({ + geometry: { + $geoIntersects: { + $geometry: { + type: "MultiLineString", + coordinates: [ [ [ -73.95852104926365, @@ -767,115 +740,173 @@ async function test_$geoIntersects(db: Database) { -73.95095412329623, 40.77543392621753, ], - [ - -73.95296019452276, - 40.779724262361626, - ], + ], + [ [ -73.95605545882601, 40.77954043344108, ], [ - -73.95852104926365, - 40.77889702821282, + -73.95296019452276, + 40.779724262361626, ], ], ], - ], + }, }, }, - }, - }).toArray(); + }).toArray(); - assert(intersectionByMultiPolygon); - assertEquals(intersectionByMultiPolygon.length, 1); - assertEquals(intersectionByMultiPolygon[0].name, "Yorkville"); + assert(intersectionByMultiLineString); + assertEquals(intersectionByMultiLineString.length, 1); + assertEquals(intersectionByMultiLineString[0].name, "Yorkville"); - const intersectionByCollection = await neighborhoods.find( - { + const intersectionByMultiPolygon = await neighborhoods.find({ geometry: { $geoIntersects: { $geometry: { - type: "GeometryCollection", - geometries: [ - { - type: "Point", - coordinates: [-73.95095412329623, 40.77543392621753], - }, - { - type: "MultiPoint", - coordinates: [ + type: "MultiPolygon", + coordinates: [ + [ + [ [ -73.958, 40.8003, ], + [ + -73.9737, + 40.7648, + ], [ -73.9498, 40.7968, ], [ - -73.9737, - 40.7648, + -73.958, + 40.8003, ], + ], + ], + [ + [ [ - -73.9814, - 40.7681, + -73.95852104926365, + 40.77889702821282, + ], + [ + -73.95095412329623, + 40.77543392621753, + ], + [ + -73.95296019452276, + 40.779724262361626, ], - ], - }, - { - type: "MultiLineString", - coordinates: [ [ + -73.95605545882601, + 40.77954043344108, + ], + [ + -73.95852104926365, + 40.77889702821282, + ], + ], + ], + ], + }, + }, + }, + }).toArray(); + + assert(intersectionByMultiPolygon); + assertEquals(intersectionByMultiPolygon.length, 1); + assertEquals(intersectionByMultiPolygon[0].name, "Yorkville"); + + const intersectionByCollection = await neighborhoods.find( + { + geometry: { + $geoIntersects: { + $geometry: { + type: "GeometryCollection", + geometries: [ + { + type: "Point", + coordinates: [-73.95095412329623, 40.77543392621753], + }, + { + type: "MultiPoint", + coordinates: [ [ - -73.96943, - 40.78519, + -73.958, + 40.8003, ], [ - -73.96082, - 40.78095, + -73.9498, + 40.7968, ], - ], - [ [ - -73.96415, - 40.79229, + -73.9737, + 40.7648, ], [ - -73.95544, - 40.78854, + -73.9814, + 40.7681, ], ], - [ + }, + { + type: "MultiLineString", + coordinates: [ [ - -73.97162, - 40.78205, + [ + -73.96943, + 40.78519, + ], + [ + -73.96082, + 40.78095, + ], ], [ - -73.96374, - 40.77715, + [ + -73.96415, + 40.79229, + ], + [ + -73.95544, + 40.78854, + ], ], - ], - [ [ - -73.9788, - 40.77247, + [ + -73.97162, + 40.78205, + ], + [ + -73.96374, + 40.77715, + ], ], [ - -73.97036, - 40.76811, + [ + -73.9788, + 40.77247, + ], + [ + -73.97036, + 40.76811, + ], ], ], - ], - }, - ], + }, + ], + }, }, }, }, - }, - ).toArray(); + ).toArray(); - assert(intersectionByCollection); - assertEquals(intersectionByCollection.length, 1); - assertEquals(intersectionByCollection[0].name, "Yorkville"); -} + assert(intersectionByCollection); + assertEquals(intersectionByCollection.length, 1); + assertEquals(intersectionByCollection[0].name, "Yorkville"); + } +}); diff --git a/tests/cases/10_command_helpers.ts b/tests/cases/10_command_helpers.ts index b8675d5..d185be7 100644 --- a/tests/cases/10_command_helpers.ts +++ b/tests/cases/10_command_helpers.ts @@ -1,20 +1,20 @@ import { MongoClient } from "../../mod.ts"; -import { assert, assertEquals } from "../test.deps.ts"; +import { assert, assertEquals, describe, it } from "../test.deps.ts"; -Deno.test({ - name: "db.dropDatabase", - fn: async (t) => { - const client = new MongoClient(); - const databaseName = `TEST_DATABASE_MUST_NOT_MATCH_${+new Date()}`; - const db = await client.connect( - `mongodb://127.0.0.1:27017/${databaseName}`, - ); - const collectioName = `${databaseName}_collection`; +describe("command helpers", () => { + it({ + name: "db.dropDatabase", + fn: async () => { + const client = new MongoClient(); + const databaseName = `TEST_DATABASE_MUST_NOT_MATCH_${+new Date()}`; + const db = await client.connect( + `mongodb://127.0.0.1:27017/${databaseName}`, + ); + const collectioName = `${databaseName}_collection`; - // To create database physically - await db.createCollection<{ foo: string }>(`${collectioName}`); + // To create database physically + await db.createCollection<{ foo: string }>(`${collectioName}`); - await t.step("test if the database is dropped", async () => { // A sanity check to test existence of the collection inside the test db assertEquals((await db.listCollectionNames()).length, 1); const result = await db.dropDatabase(); @@ -24,8 +24,8 @@ Deno.test({ // The collection inside the test db must not exist assertEquals((await db.listCollectionNames()).length, 0); - }); - client.close(); - }, + client.close(); + }, + }); }); diff --git a/tests/cases/99_cleanup.ts b/tests/cases/99_cleanup.ts deleted file mode 100644 index 6d339e1..0000000 --- a/tests/cases/99_cleanup.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { GridFSBucket } from "../../src/gridfs/bucket.ts"; -import { testWithClient } from "../common.ts"; - -testWithClient("cleanup", async (client) => { - const db = client.database("test"); - try { - await db.collection("mongo_test_users").drop().catch((e) => e); - await db.collection("mongo_test_places").drop().catch((e) => e); - await db.collection("mongo_test_positions").drop().catch((e) => e); - await db.collection("mongo_test_neighborhoods").drop().catch((e) => e); - await new GridFSBucket(db, { bucketName: "deno_logo" }) - .drop().catch((e) => e); - await new GridFSBucket(db, { bucketName: "echo" }) - .drop().catch((e) => e); - await new GridFSBucket(db, { bucketName: "metadata" }) - .drop().catch((e) => e); - await new GridFSBucket(db, { bucketName: "delete" }) - .drop().catch((e) => e); - } catch { - // pass - } -}); diff --git a/tests/common.ts b/tests/common.ts index 22eef0e..33a6737 100644 --- a/tests/common.ts +++ b/tests/common.ts @@ -2,32 +2,35 @@ import { Database, MongoClient } from "../mod.ts"; const hostname = "127.0.0.1"; -export function testWithClient( - name: string, - fn: (client: MongoClient) => void | Promise, -) { - Deno.test(name, async () => { - const client = await getClient(); - await fn(client); - client.close(); - }); +export async function getClient(): Promise { + const client = new MongoClient(); + await client.connect(`mongodb://${hostname}:27017`); + return client; } -export function testWithTestDBClient( - name: string, - fn: (db: Database) => void | Promise, -) { - Deno.test(name, async () => { - const client = await getClient(); - const db = client.database("test"); - await fn(db); - await db.collection("mongo_test_users").drop().catch((e) => e); - client.close(); - }); +export async function getTestDb(): Promise< + { client: MongoClient; database: Database } +> { + const client = await getClient(); + return { + client, + database: client.database("test"), + }; } -async function getClient(): Promise { - const client = new MongoClient(); - await client.connect(`mongodb://${hostname}:27017`); - return client; +export async function cleanTestDb( + client: MongoClient, + database: Database, + collectionNames?: string[] | string, +) { + if (typeof collectionNames === "string") { + collectionNames = [collectionNames]; + } + if (collectionNames !== undefined) { + for (const collectionName of collectionNames) { + await database.collection(collectionName).drop().catch((e) => e); + } + } + await database.dropDatabase().catch((e) => e); + client.close(); } diff --git a/tests/test.deps.ts b/tests/test.deps.ts index b69c942..f76cef7 100644 --- a/tests/test.deps.ts +++ b/tests/test.deps.ts @@ -4,10 +4,18 @@ export { assertNotEquals, assertRejects, assertThrows, -} from "https://deno.land/std@0.154.0/testing/asserts.ts"; -export { equals as bytesEquals } from "https://deno.land/std@0.154.0/bytes/equals.ts"; +} from "https://deno.land/std@0.201.0/assert/mod.ts"; +export { equals as bytesEquals } from "https://deno.land/std@0.201.0/bytes/equals.ts"; export * as semver from "https://deno.land/x/semver@v1.4.1/mod.ts"; export { readAll, readerFromStreamReader, -} from "https://deno.land/std@0.154.0/streams/mod.ts"; +} from "https://deno.land/std@0.201.0/streams/mod.ts"; +export { + afterAll, + afterEach, + beforeAll, + beforeEach, + describe, + it, +} from "https://deno.land/std@0.201.0/testing/bdd.ts"; diff --git a/tests/test.ts b/tests/test.ts index bad3542..09fa8e6 100644 --- a/tests/test.ts +++ b/tests/test.ts @@ -1,11 +1,11 @@ import "./cases/00_uri.ts"; import "./cases/01_auth.ts"; import "./cases/02_connect.ts"; -import "./cases/03_curd.ts"; +import "./cases/03_crud.ts"; import "./cases/04_indexes.ts"; import "./cases/05_srv.ts"; import "./cases/06_gridfs.ts"; import "./cases/07_worker.ts"; import "./cases/08_find_cursor.ts"; import "./cases/09_geospatial_types.ts"; -import "./cases/99_cleanup.ts"; +import "./cases/10_command_helpers.ts";