Skip to content

Commit adbcf9d

Browse files
committed
upping coverage
1 parent e63496b commit adbcf9d

8 files changed

Lines changed: 375 additions & 12 deletions

File tree

packages/inquire/tests/Delete.test.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,34 @@ describe('Delete Builder Tests', () => {
6565
expect(build.filters).to.deep.equal([['', []]]);
6666
});
6767

68-
});
68+
it('Should store JSON filters and custom selector settings', () => {
69+
//Configure the builder to use a custom JSON path notation.
70+
const del = new Delete('table');
71+
del.selector = '->';
72+
del.separator = '/';
73+
74+
//Add both equality and contains-style JSON filters.
75+
del.whereJson('__json__ = ?', [ 'meta->status', '__json__' ], 'active');
76+
del.whereJsonContains('meta->roles', [ 'admin', 'owner' ]);
77+
78+
//Confirm the builder preserved the JSON metadata for the dialect.
79+
const build = del.build();
80+
expect(build.selector).to.equal('->');
81+
expect(build.separator).to.equal('/');
82+
expect(build.json).to.deep.equal([
83+
{
84+
selector: 'meta->status',
85+
query: '__json__ = ?',
86+
replace: '__json__',
87+
values: [ 'active' ]
88+
},
89+
{
90+
selector: 'meta->roles',
91+
query: 'contains',
92+
replace: '',
93+
values: [ 'admin', 'owner' ]
94+
}
95+
]);
96+
});
97+
98+
});

packages/inquire/tests/Engine.test.ts

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,11 +335,63 @@ describe('Engine Tests', () => {
335335
expect(result).to.be.an('array').that.is.empty;
336336
});
337337

338+
it('Should allow the before hook to short-circuit the database query', async () => {
339+
//Use a resource that records whether the connection layer ran.
340+
const resource = new MockConnection();
341+
const engine = new Engine(resource);
342+
const rows = [ { id: 7 } ];
343+
344+
//Install a before hook that returns rows immediately.
345+
engine.before = async request => {
346+
expect(request.query).to.equal('SELECT 1');
347+
return rows;
348+
};
349+
350+
//Run the query through the public engine method.
351+
const result = await engine.query('SELECT 1');
352+
353+
//Confirm the hook result is returned and the connection is skipped.
354+
expect(engine.before).to.be.a('function');
355+
expect(result).to.equal(rows);
356+
expect(resource.queries).to.have.lengthOf(0);
357+
});
358+
359+
it('Should format template string queries with the dialect quote character', async () => {
360+
//Use the mock resource so the test can inspect the emitted request.
361+
const resource = new MockConnection();
362+
const engine = new Engine(resource);
363+
364+
//Run the tagged template API with backtick-quoted identifiers.
365+
await engine.sql`SELECT \`id\` FROM \`users\` WHERE id = ${1}`;
366+
367+
//Confirm the template was flattened into the final query object.
368+
expect(resource.queries[0]).to.deep.equal({
369+
query: 'SELECT "id" FROM "users" WHERE id = ?',
370+
values: [ 1 ]
371+
});
372+
});
373+
374+
it('Should pass the cascade flag through truncate queries', async () => {
375+
//Use the resource recorder to inspect the generated truncate request.
376+
const resource = new MockConnection();
377+
const engine = new Engine(resource);
378+
379+
//Run the public truncate helper with cascade enabled.
380+
await engine.truncate('sessions', true);
381+
382+
//Confirm the dialect-generated query preserved the cascade suffix.
383+
expect(resource.queries[0]).to.deep.equal({
384+
query: 'TRUNCATE TABLE "sessions" CASCADE',
385+
values: []
386+
});
387+
});
388+
338389
});
339390

340391
class MockConnection implements Connection {
341392
public dialect = Pgsql;
342393
public resource = {} as any;
394+
public queries: QueryObject[] = [];
343395

344396
lastId: string | number | undefined;
345397

@@ -382,6 +434,7 @@ class MockConnection implements Connection {
382434
* library should not care about the kind of database.
383435
*/
384436
public async query<R = unknown>(request: QueryObject) {
437+
this.queries.push(request);
385438
return [];
386439
}
387440

@@ -391,4 +444,4 @@ class MockConnection implements Connection {
391444
public async transaction<R = unknown>(callback: Transaction<R>) {
392445
return [] as R;
393446
}
394-
}
447+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, it } from 'mocha';
2+
import { expect } from 'chai';
3+
//NOTE: no extensions in tests because it's excluded in tsconfig.json and
4+
//we are testing in a typescript environment via `ts-mocha -r tsx` (esm)
5+
import Exception from '../src/Exception';
6+
import {
7+
backSlashes,
8+
doubleQuotes,
9+
escapeBackSlashes,
10+
escapeDoubleQuotes,
11+
isIndex,
12+
joinTypes,
13+
jsonCompare,
14+
safeJsonValue
15+
} from '../src/helpers';
16+
17+
describe('Helper Tests', () => {
18+
it('Should escape JSON-sensitive string values', () => {
19+
//Use simple inputs so each escaping rule is easy to verify.
20+
const slashValue = 'C:\\path';
21+
const quoteValue = '"quoted"';
22+
const safeValue = 'C:\\path "quoted"';
23+
24+
//Run each helper directly so the coverage reflects the exported API.
25+
const withSlashes = escapeBackSlashes(slashValue);
26+
const withQuotes = escapeDoubleQuotes(quoteValue);
27+
const safeJson = safeJsonValue(safeValue);
28+
29+
//Confirm each helper only applies the escaping it owns.
30+
expect(withSlashes).to.equal('C:\\\\path');
31+
expect(withQuotes).to.equal('\\"quoted\\"');
32+
expect(safeJson).to.equal('C:\\\\path \\"quoted\\"');
33+
});
34+
35+
it('Should expose the shared helper constants and compare JSON values', () => {
36+
//Exercise the exported regex helpers and join mapping directly.
37+
expect(joinTypes.full_outer).to.equal('FULL OUTER');
38+
expect(isIndex.test('12')).to.equal(true);
39+
expect(isIndex.test('id')).to.equal(false);
40+
expect('a\\b'.replace(backSlashes, '/')).to.equal('a/b');
41+
expect('"a"'.replace(doubleQuotes, '\'')).to.equal('\'a\'');
42+
43+
//Cover both the equal and not-equal comparison branches.
44+
expect(jsonCompare({ id: 1, tags: [ 'a' ] }, { id: 1, tags: [ 'a' ] }))
45+
.to.equal(true);
46+
expect(jsonCompare({ id: 1 }, { id: 2 })).to.equal(false);
47+
});
48+
49+
it('Should expose the custom exception class', () => {
50+
//Instantiate the package-specific error directly.
51+
const error = new Exception('Coverage error');
52+
53+
//Confirm callers receive the package-specific exception type.
54+
expect(error).to.be.instanceOf(Exception);
55+
expect(error).to.be.instanceOf(Error);
56+
expect(error.message).to.equal('Coverage error');
57+
});
58+
});

packages/inquire/tests/Mysql.test.ts

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Delete from '../src/builder/Delete';
88
import Insert from '../src/builder/Insert';
99
import Select from '../src/builder/Select';
1010
import Update from '../src/builder/Update';
11-
import Mysql from '../src/dialect/Mysql';
11+
import Mysql, { MysqlDialect } from '../src/dialect/Mysql';
1212

1313
describe('Mysql Dialect Tests', () => {
1414
it('Should translate alter', async () => {
@@ -399,4 +399,64 @@ describe('Mysql Dialect Tests', () => {
399399

400400
expect(query[0].values).to.be.empty;
401401
});
402-
});
402+
403+
it('Should translate JSON selectors in select queries', () => {
404+
//Use a fresh dialect instance so selector mutations stay local.
405+
const dialect = new MysqlDialect();
406+
const select = new Select([
407+
{ column: 'profile.data:info.name', alias: 'displayName' },
408+
{ column: 'profile.data:tags.0' }
409+
]);
410+
411+
//Build a query that touches selector, where, JSON where, contains, and sort.
412+
select.from({ name: 'users', alias: 'u' });
413+
select.where('profile.data:info.name = ?', [ 'Ada' ]);
414+
select.whereJson(
415+
'__json__ = ?',
416+
[ 'profile.data:info.name', '__json__' ],
417+
'Ada'
418+
);
419+
select.whereJsonContains('profile.data:tags', [ 'admin', 'owner' ]);
420+
select.order({ name: 'profile.data:info.name' }, 'desc');
421+
422+
//Confirm the dialect rewrote JSON selectors consistently.
423+
const query = dialect.select(select);
424+
expect(query.query).to.contain('JSON_UNQUOTE(JSON_EXTRACT(`profile`.`data`,');
425+
expect(query.query).to.contain('$."info"."name"');
426+
expect(query.query).to.contain('$."tags"[0]');
427+
expect(query.query).to.contain(
428+
"JSON_CONTAINS(`profile`.`data`, '$.\"tags\"')"
429+
);
430+
expect(query.query).to.contain('ORDER BY');
431+
expect(query.values).to.deep.equal([
432+
'Ada',
433+
'Ada',
434+
'"admin"',
435+
'"owner"'
436+
]);
437+
});
438+
439+
it('Should expose configurable JSON dialect helpers', () => {
440+
//Read and write the shared JsonTrait configuration through Mysql.
441+
const dialect = new MysqlDialect();
442+
dialect.separator = '/';
443+
dialect.splitter = '->';
444+
445+
//Create helpers through both overloads so parse branches are covered.
446+
const parsed = dialect.json('profile.data->info/name', '->', '/');
447+
const direct = dialect.json('profile.data', [ 'tags', '0' ]);
448+
449+
//Confirm the helper metadata matches the expected MySQL JSON syntax.
450+
expect(dialect.separator).to.equal('/');
451+
expect(dialect.splitter).to.equal('->');
452+
expect(parsed.extract).to.equal(
453+
"JSON_UNQUOTE(JSON_EXTRACT(`profile`.`data`, '$.\"info\".\"name\"'))"
454+
);
455+
expect(parsed.where('__json__ = ?', '__json__')).to.equal(
456+
"JSON_UNQUOTE(JSON_EXTRACT(`profile`.`data`, '$.\"info\".\"name\"')) = ?"
457+
);
458+
expect(direct.contains).to.equal(
459+
"JSON_CONTAINS(`profile`.`data`, '$.\"tags\"[0]')"
460+
);
461+
});
462+
});

packages/inquire/tests/Pgsql.test.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import Delete from '../src/builder/Delete';
88
import Insert from '../src/builder/Insert';
99
import Select from '../src/builder/Select';
1010
import Update from '../src/builder/Update';
11-
import Pgsql from '../src/dialect/Pgsql';
11+
import Pgsql, { PgsqlDialect } from '../src/dialect/Pgsql';
1212

1313
describe('Pgsql Dialect Tests', () => {
1414
it('Should translate alter', async () => {
@@ -436,4 +436,51 @@ describe('Pgsql Dialect Tests', () => {
436436

437437
expect(query[0].values).to.be.empty;
438438
});
439-
});
439+
440+
it('Should translate JSON selectors in select queries', () => {
441+
//Keep JSON trait mutations isolated to this test instance.
442+
const dialect = new PgsqlDialect();
443+
const select = new Select([
444+
{ column: 'profile.data:info.name', alias: 'displayName' },
445+
{ column: 'profile.data:tags.0' }
446+
]);
447+
448+
//Exercise raw where clauses, JSON equality filters, contains filters, and sort.
449+
select.from({ name: 'users', alias: 'u' });
450+
select.where('profile.data:info.name = ?', [ 'Ada' ]);
451+
select.whereJson(
452+
'__json__ = ?',
453+
[ 'profile.data:info.name', '__json__' ],
454+
'Ada'
455+
);
456+
select.whereJsonContains('profile.data:tags', [ 'admin', 'owner' ]);
457+
select.order({ name: 'profile.data:info.name' }, 'desc');
458+
459+
//Confirm the PostgreSQL dialect rewrote the JSON selectors.
460+
const query = dialect.select(select);
461+
expect(query.query).to.contain('"profile"."data"->$$info$$->>$$name$$');
462+
expect(query.query).to.contain('"profile"."data"->$$tags$$->>0');
463+
expect(query.query).to.contain('"profile"."data"->$$tags$$ ?? ?');
464+
expect(query.query).to.contain('ORDER BY');
465+
expect(query.values).to.deep.equal([ 'Ada', 'Ada', 'admin', 'owner' ]);
466+
});
467+
468+
it('Should expose configurable JSON dialect helpers', () => {
469+
//Change both JsonTrait delimiters and resolve helper objects through both overloads.
470+
const dialect = new PgsqlDialect();
471+
dialect.separator = '/';
472+
dialect.splitter = '->';
473+
474+
const parsed = dialect.json('profile.data->info/name', '->', '/');
475+
const direct = dialect.json('profile.data', [ 'tags', '0' ]);
476+
477+
//Confirm the helper API produces the PostgreSQL-specific expressions.
478+
expect(dialect.separator).to.equal('/');
479+
expect(dialect.splitter).to.equal('->');
480+
expect(parsed.extract).to.equal('"profile"."data"->$$info$$->>$$name$$');
481+
expect(parsed.where('__json__ = ?', '__json__')).to.equal(
482+
'"profile"."data"->$$info$$->>$$name$$ = ?'
483+
);
484+
expect(direct.contains).to.equal('"profile"."data"->$$tags$$->0 ?? ?');
485+
});
486+
});

packages/inquire/tests/Select.test.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,37 @@ describe('Select Builder Tests', () => {
104104
expect(filters[0].clause).to.equal('');
105105
expect(filters[0].values).to.deep.equal([]);
106106
});
107-
108107

108+
it('Should split comma-separated selectors and preserve JSON filter metadata', () => {
109+
//Start with a raw selector string so the split-and-trim branch runs.
110+
const select = new Select(' id, profile.data:info.name , , count(*) ');
109111

110-
});
112+
//Add JSON filters to exercise the builder-only storage logic.
113+
select.whereJson('__json__ = ?', [ 'profile.data:info.name', '__json__' ], 'Ada');
114+
select.whereJsonContains('profile.data:tags', 'admin');
115+
116+
//Confirm blank selectors were removed and JSON filters were stored.
117+
const build = select.build();
118+
expect(build.selectors).to.deep.equal([
119+
'id',
120+
'profile.data:info.name',
121+
'count(*)'
122+
]);
123+
expect(build.json).to.deep.equal([
124+
{
125+
selector: 'profile.data:info.name',
126+
query: '__json__ = ?',
127+
replace: '__json__',
128+
values: [ 'Ada' ]
129+
},
130+
{
131+
selector: 'profile.data:tags',
132+
query: 'contains',
133+
replace: '',
134+
values: [ 'admin' ]
135+
}
136+
]);
137+
});
138+
139+
140+
});

0 commit comments

Comments
 (0)