UNPKG

pure-orm

Version:

A SQL Toolkit based on pure business objects passed to and from stateful data access objects

553 lines (552 loc) 25.5 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable max-len */ const index_1 = require("./index"); const entities_1 = require("../test-utils/order/entities"); const order_1 = require("../test-utils/order/models/order"); const mockPgpDb = (overrides = {}) => (Object.assign({ $config: { pgp: true }, many: jest.fn(), any: jest.fn(), result: jest.fn(), none: jest.fn() }, overrides)); /* -------------------------------------------------------------------------*/ /* Helper Utility Functions ------------------------------------------------*/ /* -------------------------------------------------------------------------*/ describe('getSqlInsertParts', () => { test('generates correct insert parts for a model with defined values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'test@test.com', subtotalPrice: 100, utmSourceId: 10 }); expect(orm.getSqlInsertParts(order)).toEqual({ columns: '"id", "email", "subtotal_price", "utm_source_id"', values: [1, 'test@test.com', 100, 10], valuesVar: ['$1', '$2', '$3', '$4'] }); }); test('filters out undefined properties', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 5 }); const result = orm.getSqlInsertParts(order); expect(result.columns).toEqual('"id"'); expect(result.values).toEqual([5]); expect(result.valuesVar).toEqual(['$1']); }); test('includes all defined properties', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'a@b.com', browserIp: '1.2.3.4', subtotalPrice: 50, totalPrice: 55 }); const result = orm.getSqlInsertParts(order); expect(result.values).toContain(1); expect(result.values).toContain('a@b.com'); expect(result.values).toContain(50); expect(result.values).toContain(55); expect(result.values.length).toEqual(result.valuesVar.length); }); }); describe('getSqlUpdateParts', () => { test('generates correct update parts', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'test@test.com', subtotalPrice: 100, utmSourceId: 10 }); expect(orm.getSqlUpdateParts(order)).toEqual({ clause: '"id" = $1, "email" = $2, "subtotal_price" = $3, "utm_source_id" = $4', idVar: '$5', values: [1, 'test@test.com', 100, 10, 1] }); }); test('appends the id value at the end of values array', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 42, email: 'x@y.com' }); const result = orm.getSqlUpdateParts(order); expect(result.values[result.values.length - 1]).toEqual(42); }); test('custom "on" parameter uses that property for the id value', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'x@y.com' }); const result = orm.getSqlUpdateParts(order, 'email'); expect(result.values[result.values.length - 1]).toEqual('x@y.com'); }); test('filters out undefined properties from clause', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 7 }); const result = orm.getSqlUpdateParts(order); expect(result.clause).toEqual('"id" = $1'); expect(result.idVar).toEqual('$2'); expect(result.values).toEqual([7, 7]); }); }); describe('getMatchingParts', () => { test('generates correct WHERE clause and values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'test@test.com', subtotalPrice: 100, utmSourceId: 10 }); expect(orm.getMatchingParts(order)).toEqual({ values: [1, 'test@test.com', 100, 10], whereClause: '"order"."id" = $1 AND "order"."email" = $2 AND "order"."subtotal_price" = $3 AND "order"."utm_source_id" = $4' }); }); test('filters out null and undefined values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); const result = orm.getMatchingParts(order); expect(result.whereClause).toEqual('"order"."id" = $1'); expect(result.values).toEqual([1]); }); }); describe('getMatchingPartsObject', () => { test('generates correct WHERE clause and object values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'test@test.com', subtotalPrice: 100, utmSourceId: 10 }); expect(orm.getMatchingPartsObject(order)).toEqual({ values: { 1: 1, 2: 'test@test.com', 3: 100, 4: 10 }, whereClause: '"order"."id" = $(1) AND "order"."email" = $(2) AND "order"."subtotal_price" = $(3) AND "order"."utm_source_id" = $(4)' }); }); test('uses $() syntax for parameterized values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 5 }); const result = orm.getMatchingPartsObject(order); expect(result.whereClause).toContain('$(1)'); expect(result.values).toEqual({ 1: 5 }); }); }); describe('getValueBySqlColumn', () => { test('retrieves values by SQL column name', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1, email: 'test@test.com', subtotalPrice: 100, utmSourceId: 10 }); expect(orm.getValueBySqlColumn(order, 'id')).toEqual(1); expect(orm.getValueBySqlColumn(order, 'email')).toEqual('test@test.com'); expect(orm.getValueBySqlColumn(order, 'subtotal_price')).toEqual(100); expect(orm.getValueBySqlColumn(order, 'utm_source_id')).toEqual(10); }); test('returns undefined for columns with no value set', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); expect(orm.getValueBySqlColumn(order, 'email')).toBeUndefined(); }); }); describe('getSqlColumnForPropertyName', () => { test('maps property names to SQL column names', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); expect(orm.getSqlColumnForPropertyName(order, 'id')).toEqual('id'); expect(orm.getSqlColumnForPropertyName(order, 'utmSourceId')).toEqual('utm_source_id'); expect(orm.getSqlColumnForPropertyName(order, 'browserIP')).toEqual('browser_ip'); }); test('returns undefined for non-existent property name', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); expect(orm.getSqlColumnForPropertyName(order, 'nonExistent')).toBeUndefined(); }); }); describe('getNewWith', () => { test('creates a new model instance with specified SQL columns and values', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); const newOrder = orm.getNewWith(order, ['id', 'email'], [99, 'new@test.com']); expect(newOrder).toBeInstanceOf(order_1.Order); expect(newOrder.id).toEqual(99); expect(newOrder.email).toEqual('new@test.com'); }); test('creates model with single column', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); const newOrder = orm.getNewWith(order, ['id'], [42]); expect(newOrder).toBeInstanceOf(order_1.Order); expect(newOrder.id).toEqual(42); }); test('maps SQL column names to property names', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); const order = new order_1.Order({ id: 1 }); const newOrder = orm.getNewWith(order, ['id', 'subtotal_price', 'utm_source_id'], [1, 200, 5]); expect(newOrder.id).toEqual(1); expect(newOrder.subtotalPrice).toEqual(200); expect(newOrder.utmSourceId).toEqual(5); }); }); /* -------------------------------------------------------------------------*/ /* Unsupported driver error ------------------------------------------------*/ /* -------------------------------------------------------------------------*/ describe('unsupported database driver', () => { test('throws for an unrecognized driver', () => { expect(() => (0, index_1.create)({ entities: entities_1.entities, db: { $config: {} } })).toThrow('database driver is not yet supported'); }); }); /* -------------------------------------------------------------------------*/ /* CRUD operations (mocked db) ---------------------------------------------*/ /* -------------------------------------------------------------------------*/ describe('CRUD operations', () => { const orderSelectClause = '"order".id as "order#id", "order".email as "order#email", "order".browser_ip as "order#browser_ip", "order".browser_user_agent as "order#browser_user_agent", "order".kujo_imported_date as "order#kujo_imported_date", "order".created_date as "order#created_date", "order".cancel_reason as "order#cancel_reason", "order".cancelled_date as "order#cancelled_date", "order".closed_date as "order#closed_date", "order".processed_date as "order#processed_date", "order".updated_date as "order#updated_date", "order".note as "order#note", "order".subtotal_price as "order#subtotal_price", "order".taxes_included as "order#taxes_included", "order".total_discounts as "order#total_discounts", "order".total_price as "order#total_price", "order".total_tax as "order#total_tax", "order".total_weight as "order#total_weight", "order".order_status_url as "order#order_status_url", "order".utm_source_id as "order#utm_source_id", "order".utm_medium_id as "order#utm_medium_id", "order".utm_campaign as "order#utm_campaign", "order".utm_content as "order#utm_content", "order".utm_term as "order#utm_term"'; const makeRow = (id, email) => ({ 'order#id': id, 'order#email': email, 'order#browser_ip': null, 'order#browser_user_agent': null, 'order#kujo_imported_date': null, 'order#created_date': null, 'order#cancel_reason': null, 'order#cancelled_date': null, 'order#closed_date': null, 'order#processed_date': null, 'order#updated_date': null, 'order#note': null, 'order#subtotal_price': null, 'order#taxes_included': null, 'order#total_discounts': null, 'order#total_price': null, 'order#total_tax': null, 'order#total_weight': null, 'order#order_status_url': null, 'order#utm_source_id': null, 'order#utm_medium_id': null, 'order#utm_campaign': null, 'order#utm_content': null, 'order#utm_term': null }); describe('create (INSERT)', () => { test('builds INSERT query and returns created model', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'new@test.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1, email: 'new@test.com' }); const result = yield orm.create(order); expect(db.many).toHaveBeenCalledTimes(1); const [query, values] = db.many.mock.calls[0]; expect(query).toContain('INSERT INTO "order"'); expect(query).toContain('"id", "email"'); expect(query).toContain('$1,$2'); expect(query).toContain('RETURNING'); expect(values).toEqual([1, 'new@test.com']); expect(result.id).toEqual(1); expect(result.email).toEqual('new@test.com'); })); test('only includes defined columns in INSERT', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(5, null)]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 5 }); yield orm.create(order); const [query, values] = db.many.mock.calls[0]; expect(query).toContain('"id"'); expect(query).not.toContain('"email"'); expect(values).toEqual([5]); })); }); describe('update (UPDATE)', () => { test('builds UPDATE query and returns updated model', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'updated@test.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1, email: 'updated@test.com' }); const result = yield orm.update(order); expect(db.many).toHaveBeenCalledTimes(1); const [query, values] = db.many.mock.calls[0]; expect(query).toContain('UPDATE "order"'); expect(query).toContain('SET'); expect(query).toContain('WHERE'); expect(query).toContain('RETURNING'); expect(values).toContain(1); expect(values).toContain('updated@test.com'); expect(result.email).toEqual('updated@test.com'); })); test('uses custom "on" column for WHERE clause', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'x@y.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1, email: 'x@y.com' }); yield orm.update(order, { on: 'email' }); const [query, values] = db.many.mock.calls[0]; expect(query).toContain('WHERE'); expect(query).toContain('"order".email'); expect(values[values.length - 1]).toEqual('x@y.com'); })); }); describe('delete (DELETE)', () => { test('builds DELETE query with id', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ none: jest.fn().mockResolvedValue(null) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 42 }); yield orm.delete(order); expect(db.none).toHaveBeenCalledTimes(1); const [query, values] = db.none.mock.calls[0]; expect(query).toContain('DELETE FROM "order"'); expect(query).toContain('"order".id = $(id)'); expect(values).toEqual({ id: 42 }); })); }); describe('deleteMatching', () => { test('builds DELETE query with WHERE clause from model properties', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ none: jest.fn().mockResolvedValue(null) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 10, email: 'del@test.com' }); yield orm.deleteMatching(order); expect(db.none).toHaveBeenCalledTimes(1); const [query, values] = db.none.mock.calls[0]; expect(query).toContain('DELETE FROM "order"'); expect(query).toContain('WHERE'); expect(query).toContain('"order"."id" = $1'); expect(query).toContain('"order"."email" = $2'); expect(values).toEqual([10, 'del@test.com']); })); }); describe('getMatching', () => { test('builds SELECT query with WHERE clause and returns one model', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'found@test.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1 }); const result = yield orm.getMatching(order); expect(db.many).toHaveBeenCalledTimes(1); const [query, values] = db.many.mock.calls[0]; expect(query).toContain('SELECT'); expect(query).toContain('FROM "order"'); expect(query).toContain('WHERE'); expect(query).toContain('"order"."id" = $1'); expect(values).toEqual([1]); expect(result.id).toEqual(1); expect(result.email).toEqual('found@test.com'); })); }); describe('getOneOrNoneMatching', () => { test('returns a model when one exists', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([makeRow(1, 'one@test.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1 }); const result = yield orm.getOneOrNoneMatching(order); expect(db.any).toHaveBeenCalledTimes(1); expect(result).toBeDefined(); expect(result.id).toEqual(1); })); test('returns undefined when none exist', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 999 }); const result = yield orm.getOneOrNoneMatching(order); expect(result).toBeUndefined(); })); }); describe('getAnyMatching', () => { test('returns collection when results exist', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ result: jest.fn().mockResolvedValue({ rows: [makeRow(1, 'a@test.com'), makeRow(2, 'b@test.com')], fields: [{ name: 'order#id' }] }) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1 }); const result = yield orm.getAnyMatching(order); expect(db.result).toHaveBeenCalledTimes(1); expect(result.models.length).toEqual(2); })); test('returns empty collection when no results', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ result: jest.fn().mockResolvedValue({ rows: [], fields: [{ name: 'order#id' }] }) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 999 }); const result = yield orm.getAnyMatching(order); expect(result.models.length).toEqual(0); })); }); describe('getAllMatching', () => { test('returns collection when results exist', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([makeRow(1, 'a@test.com')]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 1 }); const result = yield orm.getAllMatching(order); expect(db.any).toHaveBeenCalledTimes(1); expect(result.models.length).toEqual(1); })); test('throws when no results exist', () => __awaiter(void 0, void 0, void 0, function* () { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([]) }); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); const order = new order_1.Order({ id: 999 }); yield expect(orm.getAllMatching(order)).rejects.toThrow(); })); }); }); /* -------------------------------------------------------------------------*/ /* ORM exposes core methods ------------------------------------------------*/ /* -------------------------------------------------------------------------*/ describe('ORM exposes core methods', () => { test('exposes createFromDatabase', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.createFromDatabase).toBe('function'); }); test('exposes createOneFromDatabase', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.createOneFromDatabase).toBe('function'); }); test('exposes createOneOrNoneFromDatabase', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.createOneOrNoneFromDatabase).toBe('function'); }); test('exposes createManyFromDatabase', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.createManyFromDatabase).toBe('function'); }); test('exposes createAnyFromDatabase', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.createAnyFromDatabase).toBe('function'); }); test('exposes tables', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(orm.tables).toBeDefined(); expect(Object.keys(orm.tables).length).toEqual(5); }); test('exposes getEntityByModel', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.getEntityByModel).toBe('function'); const order = new order_1.Order({ id: 1 }); expect(orm.getEntityByModel(order).tableName).toEqual('order'); }); test('exposes getEntityByTableName', () => { const orm = (0, index_1.create)({ entities: entities_1.entities, db: mockPgpDb() }); expect(typeof orm.getEntityByTableName).toBe('function'); expect(orm.getEntityByTableName('order').tableName).toEqual('order'); }); test('exposes db reference', () => { const db = mockPgpDb(); const orm = (0, index_1.create)({ entities: entities_1.entities, db }); expect(orm.db).toBe(db); }); });