UNPKG

pure-orm

Version:

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

618 lines (569 loc) 21.1 kB
/* eslint-disable max-len */ import { create } from './index'; import { entities as orderEntities } from '../test-utils/order/entities'; import { Order } from '../test-utils/order/models/order'; import { UtmSource } from '../test-utils/order/models/utm-source'; const mockPgpDb = (overrides: any = {}) => ({ $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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new Order({ id: 1 }); expect(orm.getValueBySqlColumn(order, 'email')).toBeUndefined(); }); }); describe('getSqlColumnForPropertyName', () => { test('maps property names to SQL column names', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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 = create({ entities: orderEntities, db: mockPgpDb() }); const order = new Order({ id: 1 }); const newOrder = orm.getNewWith( order, ['id', 'email'], [99, 'new@test.com'] ); expect(newOrder).toBeInstanceOf(Order); expect(newOrder.id).toEqual(99); expect(newOrder.email).toEqual('new@test.com'); }); test('creates model with single column', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); const order = new Order({ id: 1 }); const newOrder = orm.getNewWith(order, ['id'], [42]); expect(newOrder).toBeInstanceOf(Order); expect(newOrder.id).toEqual(42); }); test('maps SQL column names to property names', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); const order = new 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(() => create({ entities: orderEntities, 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: number, email: string | null) => ({ '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', async () => { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'new@test.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1, email: 'new@test.com' }); const result = await 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', async () => { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(5, null)]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 5 }); await 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', async () => { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'updated@test.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1, email: 'updated@test.com' }); const result = await 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', async () => { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'x@y.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1, email: 'x@y.com' }); await 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', async () => { const db = mockPgpDb({ none: jest.fn().mockResolvedValue(null) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 42 }); await 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', async () => { const db = mockPgpDb({ none: jest.fn().mockResolvedValue(null) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 10, email: 'del@test.com' }); await 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', async () => { const db = mockPgpDb({ many: jest.fn().mockResolvedValue([makeRow(1, 'found@test.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1 }); const result = await 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', async () => { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([makeRow(1, 'one@test.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1 }); const result = await orm.getOneOrNoneMatching(order); expect(db.any).toHaveBeenCalledTimes(1); expect(result).toBeDefined(); expect(result!.id).toEqual(1); }); test('returns undefined when none exist', async () => { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 999 }); const result = await orm.getOneOrNoneMatching(order); expect(result).toBeUndefined(); }); }); describe('getAnyMatching', () => { test('returns collection when results exist', async () => { const db = mockPgpDb({ result: jest.fn().mockResolvedValue({ rows: [makeRow(1, 'a@test.com'), makeRow(2, 'b@test.com')], fields: [{ name: 'order#id' }] }) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1 }); const result: any = await orm.getAnyMatching(order); expect(db.result).toHaveBeenCalledTimes(1); expect(result.models.length).toEqual(2); }); test('returns empty collection when no results', async () => { const db = mockPgpDb({ result: jest.fn().mockResolvedValue({ rows: [], fields: [{ name: 'order#id' }] }) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 999 }); const result: any = await orm.getAnyMatching(order); expect(result.models.length).toEqual(0); }); }); describe('getAllMatching', () => { test('returns collection when results exist', async () => { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([makeRow(1, 'a@test.com')]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 1 }); const result: any = await orm.getAllMatching(order); expect(db.any).toHaveBeenCalledTimes(1); expect(result.models.length).toEqual(1); }); test('throws when no results exist', async () => { const db = mockPgpDb({ any: jest.fn().mockResolvedValue([]) }); const orm = create({ entities: orderEntities, db }); const order = new Order({ id: 999 }); await expect(orm.getAllMatching(order)).rejects.toThrow(); }); }); }); /* -------------------------------------------------------------------------*/ /* ORM exposes core methods ------------------------------------------------*/ /* -------------------------------------------------------------------------*/ describe('ORM exposes core methods', () => { test('exposes createFromDatabase', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.createFromDatabase).toBe('function'); }); test('exposes createOneFromDatabase', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.createOneFromDatabase).toBe('function'); }); test('exposes createOneOrNoneFromDatabase', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.createOneOrNoneFromDatabase).toBe('function'); }); test('exposes createManyFromDatabase', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.createManyFromDatabase).toBe('function'); }); test('exposes createAnyFromDatabase', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.createAnyFromDatabase).toBe('function'); }); test('exposes tables', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(orm.tables).toBeDefined(); expect(Object.keys(orm.tables).length).toEqual(5); }); test('exposes getEntityByModel', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.getEntityByModel).toBe('function'); const order = new Order({ id: 1 }); expect(orm.getEntityByModel(order).tableName).toEqual('order'); }); test('exposes getEntityByTableName', () => { const orm = create({ entities: orderEntities, db: mockPgpDb() }); expect(typeof orm.getEntityByTableName).toBe('function'); expect(orm.getEntityByTableName('order').tableName).toEqual('order'); }); test('exposes db reference', () => { const db = mockPgpDb(); const orm = create({ entities: orderEntities, db }); expect(orm.db).toBe(db); }); });