Skip to content

Commit 6ae5a61

Browse files
feat: Add unit and integration tests for documented features
This commit introduces new tests to cover features and scenarios highlighted in the documentation, ensuring robustness and correctness of the library's behavior. New tests include: 1. **`whereIn` with Subqueries (Unit Tests)**: * Added unit tests to `tests/unit/select.test.ts` for `whereIn` clauses that accept a subquery. * Covers scenarios where the subquery is a `SelectBuilder` instance or a `Query` object from `getQueryAll()`. * Verifies correct SQL generation and argument merging. 2. **`JOIN` with Subquery from `getQueryAll()` (Integration Test)**: * Added an integration test to `tests/integration/crud.test.ts`. * Tests a `SELECT` query joining with a subquery built using the `qb.select(...).getQueryAll()` pattern. * Verifies the correctness of fetched results against a D1 database. 3. **`raw` Queries (Integration Tests)**: * Created `tests/integration/raw.test.ts` for dedicated `qb.raw()` tests. * Covers `fetchType: 'ALL'`, `fetchType: 'ONE'` (including non-existent rows), and raw `INSERT`, `UPDATE`, and `DELETE` operations. * Verifies results and database state changes. 4. **`onConflict` Insert Variations (Integration Tests)**: * Created `tests/integration/insert.test.ts` for `onConflict` scenarios. * Tests `ON CONFLICT IGNORE`, `ON CONFLICT REPLACE` (for both unique and primary key conflicts), and UPSERT (`ON CONFLICT DO UPDATE SET ...`). * Includes tests for conditional UPSERTs with a `WHERE` clause on the `DO UPDATE` part. * Verifies database behavior and returned metadata. 5. **Enhanced Migration Tests (Integration Tests)**: * Improved `tests/integration/migrations-d1.test.ts`. * Added explicit tests for `getApplied()` and `getUnapplied()` methods. * Added tests for the migration system's behavior with an empty `migrations` array. * Refactored existing migration tests with a `beforeEach` hook for cleaner state management and standardized migration definitions.
1 parent 2783082 commit 6ae5a61

File tree

11 files changed

+1045
-164
lines changed

11 files changed

+1045
-164
lines changed

docs/.vitepress/config.mts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export default defineConfig({
99
themeConfig: {
1010
// https://vitepress.dev/reference/default-theme-config
1111
logo: 'https://raw.githubusercontent.com/G4brym/workers-qb/refs/heads/main/docs/assets/logo-icon.png',
12+
outline: 'deep',
1213
nav: [
1314
{text: 'Home', link: '/'},
1415
{text: 'Docs', link: '/introduction'}

docs/advanced-queries.md

Lines changed: 150 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ An INNER JOIN returns rows only when there is a match in both tables based on th
1313
```typescript
1414
import { D1QB } from 'workers-qb';
1515

16-
// ... (D1QB initialization) ...
16+
// Example D1QB initialization:
17+
// interface Env {
18+
// DB: D1Database;
19+
// }
20+
// const env: Env = /* your environment object */;
21+
// const qb = new D1QB(env.DB);
1722

1823
type UserWithRole = {
1924
userName: string;
@@ -40,7 +45,12 @@ A LEFT JOIN (or LEFT OUTER JOIN) returns all rows from the left table and the ma
4045
```typescript
4146
import { D1QB } from 'workers-qb';
4247

43-
// ... (D1QB initialization) ...
48+
// Example D1QB initialization:
49+
// interface Env {
50+
// DB: D1Database;
51+
// }
52+
// const env: Env = /* your environment object */;
53+
// const qb = new D1QB(env.DB);
4454

4555
type UserWithOptionalRole = {
4656
userName: string;
@@ -67,7 +77,12 @@ A CROSS JOIN returns the Cartesian product of rows from the tables in the join.
6777
```typescript
6878
import { D1QB } from 'workers-qb';
6979

70-
// ... (D1QB initialization) ...
80+
// Example D1QB initialization:
81+
// interface Env {
82+
// DB: D1Database;
83+
// }
84+
// const env: Env = /* your environment object */;
85+
// const qb = new D1QB(env.DB);
7186

7287
type UserAndProduct = {
7388
userName: string;
@@ -94,7 +109,12 @@ You can also use subqueries as tables in your JOIN clauses.
94109
```typescript
95110
import { D1QB } from 'workers-qb';
96111

97-
// ... (D1QB initialization) ...
112+
// Example D1QB initialization:
113+
// interface Env {
114+
// DB: D1Database;
115+
// }
116+
// const env: Env = /* your environment object */;
117+
// const qb = new D1QB(env.DB);
98118

99119
type UserWithOrderCount = {
100120
userName: string;
@@ -131,7 +151,12 @@ The `select()` method initiates a `SelectBuilder` instance, allowing you to prog
131151
```typescript
132152
import { D1QB } from 'workers-qb';
133153

134-
// ... (D1QB initialization) ...
154+
// Example D1QB initialization:
155+
// interface Env {
156+
// DB: D1Database;
157+
// }
158+
// const env: Env = /* your environment object */;
159+
// const qb = new D1QB(env.DB);
135160

136161
const selectBuilder = qb.select('users'); // Start building a select query on 'users' table
137162
```
@@ -152,7 +177,12 @@ You can chain various methods on the `SelectBuilder` to define different parts o
152177
```typescript
153178
import { D1QB } from 'workers-qb';
154179

155-
// ... (D1QB initialization) ...
180+
// Example D1QB initialization:
181+
// interface Env {
182+
// DB: D1Database;
183+
// }
184+
// const env: Env = /* your environment object */;
185+
// const qb = new D1QB(env.DB);
156186

157187
type UserInfo = {
158188
name: string;
@@ -187,7 +217,19 @@ The `SelectBuilder` provides methods to execute the built query and retrieve res
187217
```typescript
188218
import { D1QB } from 'workers-qb';
189219

190-
// ... (D1QB initialization) ...
220+
// Example D1QB initialization:
221+
// interface Env {
222+
// DB: D1Database;
223+
// }
224+
// const env: Env = /* your environment object */;
225+
// const qb = new D1QB(env.DB);
226+
227+
// Assuming UserInfo is defined as in the previous example:
228+
// type UserInfo = {
229+
// name: string;
230+
// email: string;
231+
// roleName: string;
232+
// };
191233

192234
const activeUserCount = await qb.select('users')
193235
.where('is_active = ?', true)
@@ -215,7 +257,12 @@ You can use a simple string to define your WHERE clause. Use `?` placeholders fo
215257
```typescript
216258
import { D1QB } from 'workers-qb';
217259

218-
// ... (D1QB initialization) ...
260+
// Example D1QB initialization:
261+
// interface Env {
262+
// DB: D1Database;
263+
// }
264+
// const env: Env = /* your environment object */;
265+
// const qb = new D1QB(env.DB);
219266

220267
const usersByName = await qb.fetchAll({
221268
tableName: 'users',
@@ -233,7 +280,12 @@ For more structured conditions, you can use an object with `conditions` and `par
233280
```typescript
234281
import { D1QB } from 'workers-qb';
235282

236-
// ... (D1QB initialization) ...
283+
// Example D1QB initialization:
284+
// interface Env {
285+
// DB: D1Database;
286+
// }
287+
// const env: Env = /* your environment object */;
288+
// const qb = new D1QB(env.DB);
237289

238290
const usersByRoleAndActive = await qb.fetchAll({
239291
tableName: 'users',
@@ -256,13 +308,22 @@ The `whereIn` method provides a convenient way to filter records based on a set
256308
```typescript
257309
import { D1QB } from 'workers-qb';
258310

259-
// ... (D1QB initialization) ...
311+
// Example D1QB initialization:
312+
// interface Env {
313+
// DB: D1Database;
314+
// }
315+
// const env: Env = /* your environment object */;
316+
// const qb = new D1QB(env.DB);
260317

261318
const usersInRoles = await qb.fetchAll({
262319
tableName: 'users',
263-
where: qb.select('roles').fields('id').where('is_admin = ?', true).getQueryAll(), // Subquery to get admin role IDs
264-
}).execute() // Error! 'where' option should be 'whereIn' for IN clause
320+
// The following 'where' is NOT the correct way to use a subquery for an IN clause.
321+
// It would result in a query like: SELECT * FROM users WHERE (SELECT id FROM roles WHERE is_admin = ?).
322+
// This is syntactically incorrect for most SQL databases for an IN condition.
323+
where: qb.select('roles').fields('id').where('is_admin = ?', true).getQueryAll(),
324+
}).execute() // This will likely lead to a SQL error.
265325

326+
// Correct way to use whereIn with a list of values:
266327
const usersInSpecificRoles = await qb.select('users')
267328
.whereIn('role_id', [1, 2, 3]) // Filter users with role_id in [1, 2, 3]
268329
.execute();
@@ -274,9 +335,17 @@ const usersInSpecificRolesMultipleColumns = await qb.select('users')
274335
.execute();
275336

276337
console.log('Users in specific role and department combinations:', usersInSpecificRolesMultipleColumns.results);
338+
339+
// Correct way to use whereIn with a subquery:
340+
const adminRoleIdsSubquery = qb.select('roles').fields('id').where('is_admin = ?', true);
341+
const usersWhoAreAdmins = await qb.select('users')
342+
.whereIn('role_id', adminRoleIdsSubquery) // Subquery for IN clause
343+
.execute();
344+
345+
console.log('Users who are admins:', usersWhoAreAdmins.results);
277346
```
278347

279-
**Important:** Note the corrected example using `whereIn` method on the `SelectBuilder` for IN clauses, instead of trying to put a subquery directly into the `where` option, which is not intended for `IN` clause subqueries in this builder's API design.
348+
**Important:** When using `whereIn`, provide either an array of values or a `SelectBuilder` instance for a subquery. Do not pass the result of `getQueryAll()` from a subquery directly into the `where` option if you intend to create an `IN` clause; use the `whereIn` method with the `SelectBuilder` instance itself.
280349

281350
## Group By and Having
282351

@@ -287,7 +356,12 @@ Use the `groupBy` method to group rows with the same values in one or more colum
287356
```typescript
288357
import { D1QB } from 'workers-qb';
289358

290-
// ... (D1QB initialization) ...
359+
// Example D1QB initialization:
360+
// interface Env {
361+
// DB: D1Database;
362+
// }
363+
// const env: Env = /* your environment object */;
364+
// const qb = new D1QB(env.DB);
291365

292366
type RoleUserCount = {
293367
roleName: string;
@@ -315,7 +389,12 @@ The `having` method filters groups based on aggregate functions, similar to WHER
315389
```typescript
316390
import { D1QB } from 'workers-qb';
317391

318-
// ... (D1QB initialization) ...
392+
// Example D1QB initialization:
393+
// interface Env {
394+
// DB: D1Database;
395+
// }
396+
// const env: Env = /* your environment object */;
397+
// const qb = new D1QB(env.DB);
319398

320399
type RoleUserCount = {
321400
roleName: string;
@@ -346,7 +425,12 @@ Use the `orderBy` method to sort the result set by one or more columns. By defau
346425
```typescript
347426
import { D1QB } from 'workers-qb';
348427

349-
// ... (D1QB initialization) ...
428+
// Example D1QB initialization:
429+
// interface Env {
430+
// DB: D1Database;
431+
// }
432+
// const env: Env = /* your environment object */;
433+
// const qb = new D1QB(env.DB);
350434

351435
type User = {
352436
id: number;
@@ -369,7 +453,19 @@ Specify the sorting direction (ASC for ascending, DESC for descending) for each
369453
```typescript
370454
import { D1QB } from 'workers-qb';
371455

372-
// ... (D1QB initialization) ...
456+
// Example D1QB initialization:
457+
// interface Env {
458+
// DB: D1Database;
459+
// }
460+
// const env: Env = /* your environment object */;
461+
// const qb = new D1QB(env.DB);
462+
463+
// Assuming User type is defined as in the previous example:
464+
// type User = {
465+
// id: number;
466+
// name: string;
467+
// email: string;
468+
// };
373469

374470
const usersOrderedByNameDesc = await qb.fetchAll<User>({
375471
tableName: 'users',
@@ -386,7 +482,19 @@ Order by multiple columns by providing an array or an object to `orderBy`.
386482
```typescript
387483
import { D1QB } from 'workers-qb';
388484

389-
// ... (D1QB initialization) ...
485+
// Example D1QB initialization:
486+
// interface Env {
487+
// DB: D1Database;
488+
// }
489+
// const env: Env = /* your environment object */;
490+
// const qb = new D1QB(env.DB);
491+
492+
// Assuming User type is defined as in the previous example:
493+
// type User = {
494+
// id: number;
495+
// name: string;
496+
// email: string;
497+
// };
390498

391499
const usersOrderedByRoleNameAndName = await qb.fetchAll<User>({
392500
tableName: 'users',
@@ -408,7 +516,12 @@ Use the `limit` method to restrict the number of rows returned by the query.
408516
```typescript
409517
import { D1QB } from 'workers-qb';
410518

411-
// ... (D1QB initialization) ...
519+
// Example D1QB initialization:
520+
// interface Env {
521+
// DB: D1Database;
522+
// }
523+
// const env: Env = /* your environment object */;
524+
// const qb = new D1QB(env.DB);
412525

413526
type User = {
414527
id: number;
@@ -431,7 +544,12 @@ Use the `offset` method to skip a certain number of rows before starting to retu
431544
```typescript
432545
import { D1QB } from 'workers-qb';
433546

434-
// ... (D1QB initialization) ...
547+
// Example D1QB initialization:
548+
// interface Env {
549+
// DB: D1Database;
550+
// }
551+
// const env: Env = /* your environment object */;
552+
// const qb = new D1QB(env.DB);
435553

436554
type User = {
437555
id: number;
@@ -459,7 +577,12 @@ Use the `raw` method to execute a raw SQL query string. You can provide paramete
459577
```typescript
460578
import { D1QB } from 'workers-qb';
461579

462-
// ... (D1QB initialization) ...
580+
// Example D1QB initialization:
581+
// interface Env {
582+
// DB: D1Database;
583+
// }
584+
// const env: Env = /* your environment object */;
585+
// const qb = new D1QB(env.DB);
463586

464587
const tableName = 'users';
465588
const columnName = 'email';
@@ -479,7 +602,12 @@ When using `raw`, you can specify the `fetchType` to indicate whether you expect
479602
```typescript
480603
import { D1QB } from 'workers-qb';
481604

482-
// ... (D1QB initialization) ...
605+
// Example D1QB initialization:
606+
// interface Env {
607+
// DB: D1Database;
608+
// }
609+
// const env: Env = /* your environment object */;
610+
// const qb = new D1QB(env.DB);
483611

484612
type User = {
485613
id: number;

0 commit comments

Comments
 (0)