diff --git a/src/__tests__/fixtures/sql/delete-basic.ts b/src/__tests__/fixtures/sql/delete-basic.ts new file mode 100644 index 0000000..f72c133 --- /dev/null +++ b/src/__tests__/fixtures/sql/delete-basic.ts @@ -0,0 +1,27 @@ +import { C6C } from '../../../api/C6Constants'; + +export default { + description: 'builds DELETE with JOIN and WHERE', + method: C6C.DELETE, + table: 'actor', + rest: { + [C6C.JOIN]: { + [C6C.INNER]: { + 'film_actor fa': { + 'fa.actor_id': [C6C.EQUAL, 'actor.actor_id'], + }, + }, + }, + [C6C.WHERE]: { + 'actor.actor_id': [C6C.GREATER_THAN, 100], + }, + }, + expected: { + sqlIncludes: [ + 'DELETE `actor` FROM `actor`', + 'INNER JOIN `film_actor` AS `fa` ON', + '(actor.actor_id) > ?', + ], + params: [100], + }, +} as const; diff --git a/src/__tests__/fixtures/sql/insert-basic.ts b/src/__tests__/fixtures/sql/insert-basic.ts new file mode 100644 index 0000000..88cc8b8 --- /dev/null +++ b/src/__tests__/fixtures/sql/insert-basic.ts @@ -0,0 +1,22 @@ +import { C6C } from '../../../api/C6Constants'; + +export default { + description: 'builds INSERT with ON DUPLICATE KEY UPDATE', + method: C6C.POST, + table: 'actor', + rest: { + [C6C.REPLACE]: { + 'actor.first_name': 'BOB', + 'actor.last_name': 'SMITH', + }, + [C6C.UPDATE]: ['first_name', 'last_name'], + }, + expected: { + sqlIncludes: [ + 'REPLACE INTO `actor`', + '`first_name`, `last_name`', + 'ON DUPLICATE KEY UPDATE `first_name` = VALUES(`first_name`), `last_name` = VALUES(`last_name`)', + ], + params: ['BOB', 'SMITH'], + }, +} as const; diff --git a/src/__tests__/fixtures/sql/select-basic.ts b/src/__tests__/fixtures/sql/select-basic.ts new file mode 100644 index 0000000..d80796e --- /dev/null +++ b/src/__tests__/fixtures/sql/select-basic.ts @@ -0,0 +1,39 @@ +import { C6C } from '../../../api/C6Constants'; + +export default { + description: 'builds SELECT with JOIN, WHERE, GROUP BY, HAVING and default LIMIT', + method: C6C.GET, + table: 'actor', + rest: { + [C6C.SELECT]: ['actor.first_name', [C6C.COUNT, 'actor.actor_id', C6C.AS, 'cnt']], + [C6C.JOIN]: { + [C6C.INNER]: { + 'film_actor fa': { + 'fa.actor_id': [C6C.EQUAL, 'actor.actor_id'], + }, + }, + }, + [C6C.WHERE]: { + 'actor.first_name': [C6C.LIKE, '%A%'], + 0: { + 'actor.actor_id': [C6C.GREATER_THAN, 10], + }, + }, + [C6C.GROUP_BY]: 'actor.first_name', + [C6C.HAVING]: { + cnt: [C6C.GREATER_THAN, 1], + }, + }, + expected: { + sqlIncludes: [ + 'SELECT actor.first_name, COUNT(actor.actor_id) AS cnt FROM `actor`', + 'INNER JOIN `film_actor` AS `fa` ON', + '(actor.first_name) LIKE ?', + '(actor.actor_id) > ?', + 'GROUP BY actor.first_name', + 'HAVING', + 'LIMIT 100', + ], + params: ['%A%', 10, 1], + }, +} as const; diff --git a/src/__tests__/fixtures/sql/select-binary.ts b/src/__tests__/fixtures/sql/select-binary.ts new file mode 100644 index 0000000..d487671 --- /dev/null +++ b/src/__tests__/fixtures/sql/select-binary.ts @@ -0,0 +1,17 @@ +import { C6C } from '../../../api/C6Constants'; +import { Buffer } from 'node:buffer'; + +export default { + description: 'converts hex to Buffer for BINARY columns in WHERE params', + method: C6C.GET, + table: 'actor', + rest: { + [C6C.WHERE]: { + 'actor.binarycol': [C6C.EQUAL, '0123456789abcdef0123456789abcdef'], + }, + }, + expected: { + sqlIncludes: ['WHERE (actor.binarycol) = ?'], + params: [Buffer.from('0123456789abcdef0123456789abcdef', 'hex')], + }, +} as const; diff --git a/src/__tests__/fixtures/sql/update-basic.ts b/src/__tests__/fixtures/sql/update-basic.ts new file mode 100644 index 0000000..d7b534f --- /dev/null +++ b/src/__tests__/fixtures/sql/update-basic.ts @@ -0,0 +1,25 @@ +import { C6C } from '../../../api/C6Constants'; + +export default { + description: 'builds UPDATE with WHERE and pagination', + method: C6C.PUT, + table: 'actor', + rest: { + [C6C.UPDATE]: { + 'first_name': 'ALICE', + }, + [C6C.WHERE]: { + 'actor.actor_id': [C6C.EQUAL, 5], + }, + [C6C.PAGINATION]: { [C6C.LIMIT]: 1 }, + }, + expected: { + sqlIncludes: [ + 'UPDATE `actor` SET', + '`first_name` = ?', + 'WHERE (actor.actor_id) = ?', + 'LIMIT 1', + ], + params: ['ALICE', 5], + }, +} as const; diff --git a/src/__tests__/sqlBuilders.fixture.test.ts b/src/__tests__/sqlBuilders.fixture.test.ts new file mode 100644 index 0000000..9620efd --- /dev/null +++ b/src/__tests__/sqlBuilders.fixture.test.ts @@ -0,0 +1,65 @@ +import { describe, it, expect } from 'vitest'; +import { SelectQueryBuilder } from '../api/orm/queries/SelectQueryBuilder'; +import { PostQueryBuilder } from '../api/orm/queries/PostQueryBuilder'; +import { UpdateQueryBuilder } from '../api/orm/queries/UpdateQueryBuilder'; +import { DeleteQueryBuilder } from '../api/orm/queries/DeleteQueryBuilder'; +import { buildTestConfig } from './fixtures/c6.fixture'; +import { readdirSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; + +interface Fixture { + description: string; + method: string; + table: string; + rest: any; + expected: { + sqlIncludes: string[]; + params: any[]; + }; +} + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const casesDir = path.join(__dirname, 'fixtures', 'sql'); +const caseFiles = readdirSync(casesDir).filter(f => f.endsWith('.ts')); + +const fixtures: Fixture[] = await Promise.all( + caseFiles.map(async file => { + const mod = await import(pathToFileURL(path.join(casesDir, file)).href); + return mod.default as Fixture; + }) +); + +describe('SQL Builder fixtures', () => { + fixtures.forEach(fixture => { + it(fixture.description, () => { + const config = buildTestConfig(); + config.requestMethod = fixture.method as any; + config.restModel = config.C6.TABLES[fixture.table]; + + let builder; + switch (fixture.method) { + case 'POST': + builder = new PostQueryBuilder(config as any, fixture.rest as any, false); + break; + case 'PUT': + builder = new UpdateQueryBuilder(config as any, fixture.rest as any, false); + break; + case 'DELETE': + builder = new DeleteQueryBuilder(config as any, fixture.rest as any, false); + break; + case 'GET': + default: + builder = new SelectQueryBuilder(config as any, fixture.rest as any, false); + break; + } + + const { sql, params } = builder.build(fixture.table); + fixture.expected.sqlIncludes.forEach(fragment => { + expect(sql).toContain(fragment); + }); + expect(params).toEqual(fixture.expected.params); + }); + }); +});