UNPKG

pg-tx

Version:

Transaction wrapper for node-postgres

147 lines 7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const pg_1 = require("pg"); const ts_mockito_1 = require("ts-mockito"); const _1 = __importDefault(require(".")); function testWithClient(makeClient, destroyClient) { let pg; beforeEach(async () => { pg = await makeClient(); await pg.query(`CREATE TABLE things ( id SERIAL PRIMARY KEY, thing TEXT NOT NULL )`); // https://dba.stackexchange.com/a/293929/200560 await pg.query(` CREATE TABLE test_defer( x int CONSTRAINT test_me UNIQUE DEFERRABLE INITIALLY DEFERRED ); `); }); afterEach(async () => { await pg.query(`DROP TABLE things`); await pg.query(`DROP TABLE test_defer`); await destroyClient(pg); }); it(`commits changes when there's no error`, async () => { await _1.default(pg, async (db) => { db.query(`INSERT INTO things (thing) VALUES ('committed')`); }); const { rows: [committed] } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'committed'`); expect(committed).toBeDefined(); expect(committed.thing).toEqual('committed'); }); it(`doesn't commit changes with forcedRollback`, async () => { await _1.default(pg, async (db) => { await db.query(`INSERT INTO things (thing) VALUES ('committed')`); }, true); const { rowCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'committed'`); expect(rowCount).toBe(0); }); it(`doesn't commit changes when there's a Node exception`, async () => { await expect(_1.default(pg, async (db) => { await db.query(`INSERT INTO things (thing) VALUES ('node_error')`); throw new Error(`node error`); })).rejects.toThrowError(`node error`); const { rowCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'node_error'`); expect(rowCount).toBe(0); }); it(`doesn't commit changes when there's a query error`, async () => { await expect(_1.default(pg, async (db) => { await db.query(`INSERT INTO things(thing) VALUES ('query_error')`); await db.query(`this query has an error`); })).rejects.toThrowErrorMatchingInlineSnapshot(`"syntax error at or near \\"this\\""`); const { rowCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'query_error'`); expect(rowCount).toBe(0); }); it(`doesn't commit changes on next tick after query error`, async () => { let laterPromise; const txPromise = _1.default(pg, async (db) => { laterPromise = (async () => { try { await txPromise; } catch (e) { // ignore error, we just needed to wait until it completed } await new Promise((resolve) => { setImmediate(resolve); }); await db.query(`INSERT INTO things(thing) VALUES ('query_error_tick')`); })(); await Promise.all([db.query(`this query has an error`), laterPromise]); }); await expect(txPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"syntax error at or near \\"this\\""`); await expect(laterPromise).rejects.toThrowErrorMatchingInlineSnapshot(`"Transaction client already released, see more: https://tinyurl.com/py2upfcw"`); const { rowCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'query_error_tick'`); expect(rowCount).toBe(0); }); it(`handles nested transactions`, async () => { await _1.default(pg, async (rootDb) => { await _1.default(rootDb, async (comittedDb) => { await comittedDb.query(`INSERT INTO things (thing) VALUES ('committed')`); }); await expect(_1.default(rootDb, async (rolledbackDb) => { await rolledbackDb.query(`INSERT INTO things (thing) VALUES ('rolledback')`); throw new Error(`rollback this block`); })).rejects.toThrowError(`rollback this block`); }); const { rowCount: comittedCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'committed'`); expect(comittedCount).toBe(1); const { rowCount: rolledbackCount } = await pg.query(`SELECT id, thing FROM things WHERE thing = 'rolledback'`); expect(rolledbackCount).toBe(0); }); it(`handles errors thrown on commit gracefully`, async () => { await expect(_1.default(pg, async (db) => { await expect(db.query(`INSERT INTO test_defer VALUES(1)`)).resolves.toEqual(expect.anything()); await expect(db.query(`INSERT INTO test_defer VALUES(1)`)).resolves.toEqual(expect.anything()); })).rejects.toThrowError(`duplicate key value violates unique constraint \"test_me\"`); }); } describe(`tx`, () => { describe('with Pool client', () => { testWithClient(async () => { const { POSTGRES_URL } = process.env; if (!POSTGRES_URL) { throw new Error('Must specify POSTGRES_URL'); } return new pg_1.Pool({ connectionString: POSTGRES_URL }); }, async (pg) => { await pg.end(); }); }); describe('with PoolClient client', () => { let pool; testWithClient(async () => { const { POSTGRES_URL } = process.env; if (!POSTGRES_URL) { throw new Error('Must specify POSTGRES_URL'); } pool = new pg_1.Pool({ connectionString: POSTGRES_URL }); return await pool.connect(); }, async (pg) => { await pg.release(); await pool.end(); }); }); it(`handles error thrown on commit gracefully`, async () => { const clientMock = ts_mockito_1.mock(); const exampleQuery = `SELECT 1`; ts_mockito_1.when(clientMock.query(`SELECT txid_current() AS "txid"`)) .thenResolve({ rows: [{ txid: '1' }] }) .thenResolve({ rows: [{ txid: '2' }] }); ts_mockito_1.when(clientMock.query(`COMMIT`)).thenReject(new Error(`db error`)); ts_mockito_1.when(clientMock.query(`ROLLBACK`)).thenResolve(); const client = ts_mockito_1.instance(clientMock); await expect(_1.default(client, async (db) => { await db.query(exampleQuery); })).rejects.toThrowError(`db error`); ts_mockito_1.verify(clientMock.query(exampleQuery, ts_mockito_1.anything(), ts_mockito_1.anything())).called(); ts_mockito_1.verify(clientMock.query(`COMMIT`)).called(); ts_mockito_1.verify(clientMock.query(`ROLLBACK`)).never(); }); }); //# sourceMappingURL=index.test.js.map