116 lines
3.5 KiB
TypeScript
116 lines
3.5 KiB
TypeScript
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
|
import * as smartdb from '../ts/index.js';
|
|
import { MongoClient } from 'mongodb';
|
|
import * as net from 'net';
|
|
|
|
let server: smartdb.SmartdbServer;
|
|
let client: MongoClient;
|
|
let port: number;
|
|
|
|
async function getFreePort(): Promise<number> {
|
|
return await new Promise((resolve, reject) => {
|
|
const probe = net.createServer();
|
|
probe.once('error', reject);
|
|
probe.listen(0, '127.0.0.1', () => {
|
|
const address = probe.address();
|
|
if (!address || typeof address === 'string') {
|
|
probe.close(() => reject(new Error('Failed to allocate TCP port')));
|
|
return;
|
|
}
|
|
probe.close(() => resolve(address.port));
|
|
});
|
|
});
|
|
}
|
|
|
|
tap.test('transactions: should start server and connect', async () => {
|
|
port = await getFreePort();
|
|
server = new smartdb.SmartdbServer({ port });
|
|
await server.start();
|
|
|
|
client = new MongoClient(`mongodb://127.0.0.1:${port}`, {
|
|
directConnection: true,
|
|
serverSelectionTimeoutMS: 5000,
|
|
});
|
|
await client.connect();
|
|
expect(server.running).toBeTrue();
|
|
});
|
|
|
|
tap.test('transactions: should still support explicit sessions', async () => {
|
|
const result = await client.db('admin').command({ startSession: 1 });
|
|
expect(result.ok).toEqual(1);
|
|
expect(result.id).toBeTruthy();
|
|
|
|
const end = await client.db('admin').command({ endSessions: [result.id] });
|
|
expect(end.ok).toEqual(1);
|
|
});
|
|
|
|
tap.test('transactions: should reject raw transaction-scoped writes before mutation', async () => {
|
|
const db = client.db('txntest');
|
|
const coll = db.collection('docs');
|
|
await coll.insertOne({ key: 'outside', value: 1 });
|
|
|
|
let threw = false;
|
|
try {
|
|
await db.command({
|
|
insert: 'docs',
|
|
documents: [{ key: 'inside-raw', value: 2 }],
|
|
startTransaction: true,
|
|
autocommit: false,
|
|
});
|
|
} catch (err: any) {
|
|
threw = true;
|
|
expect(err.code).toEqual(20);
|
|
expect(err.codeName).toEqual('IllegalOperation');
|
|
}
|
|
expect(threw).toBeTrue();
|
|
|
|
expect(await coll.countDocuments({ key: 'inside-raw' })).toEqual(0);
|
|
expect(await coll.countDocuments({ key: 'outside' })).toEqual(1);
|
|
});
|
|
|
|
tap.test('transactions: official driver transaction should fail without committing writes', async () => {
|
|
const coll = client.db('txntest').collection('driverdocs');
|
|
await coll.insertOne({ key: 'outside-driver', value: 0 });
|
|
const session = client.startSession();
|
|
|
|
let threw = false;
|
|
try {
|
|
session.startTransaction();
|
|
await coll.insertOne({ key: 'inside-driver', value: 1 }, { session });
|
|
await session.commitTransaction();
|
|
} catch (err: any) {
|
|
threw = true;
|
|
expect(err.code).toEqual(20);
|
|
expect(err.codeName).toEqual('IllegalOperation');
|
|
await session.abortTransaction().catch(() => undefined);
|
|
} finally {
|
|
await session.endSession();
|
|
}
|
|
|
|
expect(threw).toBeTrue();
|
|
expect(await coll.countDocuments({ key: 'inside-driver' })).toEqual(0);
|
|
expect(await coll.countDocuments({ key: 'outside-driver' })).toEqual(1);
|
|
});
|
|
|
|
tap.test('transactions: commit and abort commands should be explicit unsupported errors', async () => {
|
|
for (const command of [{ commitTransaction: 1 }, { abortTransaction: 1 }]) {
|
|
let threw = false;
|
|
try {
|
|
await client.db('admin').command(command);
|
|
} catch (err: any) {
|
|
threw = true;
|
|
expect(err.code).toEqual(20);
|
|
expect(err.codeName).toEqual('IllegalOperation');
|
|
}
|
|
expect(threw).toBeTrue();
|
|
}
|
|
});
|
|
|
|
tap.test('transactions: cleanup', async () => {
|
|
await client.close();
|
|
await server.stop();
|
|
expect(server.running).toBeFalse();
|
|
});
|
|
|
|
export default tap.start();
|