Skip to content

Commit 2773a28

Browse files
authored
Merge pull request #227 from cipherstash/supabase-test-run-id
fix(test): isolate supabase tests with unique test run IDs
2 parents 427a1ae + 7c664e5 commit 2773a28

File tree

6 files changed

+188
-122
lines changed

6 files changed

+188
-122
lines changed

local/postgres-entrypoint.sh

100644100755
File mode changed.

packages/drizzle/__tests__/drizzle.test.ts

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import 'dotenv/config'
22
import { protect } from '@cipherstash/protect'
33
import { and, eq, inArray, sql } from 'drizzle-orm'
4-
import { integer, pgTable, timestamp } from 'drizzle-orm/pg-core'
4+
import { integer, pgTable, text, timestamp } from 'drizzle-orm/pg-core'
55
import { drizzle } from 'drizzle-orm/postgres-js'
66
import postgres from 'postgres'
77
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
@@ -53,6 +53,7 @@ const drizzleUsersTable = pgTable('protect-ci', {
5353
},
5454
),
5555
createdAt: timestamp('created_at').defaultNow(),
56+
testRunId: text('test_run_id'),
5657
})
5758

5859
// Extract Protect.js schema from Drizzle table
@@ -61,6 +62,9 @@ const users = extractProtectSchema(drizzleUsersTable)
6162
// Hard code this as the CI database doesn't support order by on encrypted columns
6263
const SKIP_ORDER_BY_TEST = true
6364

65+
// Unique identifier for this test run to isolate data from concurrent test runs
66+
const TEST_RUN_ID = `drizzle-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
67+
6468
// Test data interface for decrypted results
6569
interface DecryptedUser {
6670
id: number
@@ -148,9 +152,15 @@ beforeAll(async () => {
148152
throw new Error(`Encryption failed: ${encryptedUser.failure.message}`)
149153
}
150154

155+
// Add test_run_id to each record for test isolation
156+
const dataWithTestRunId = encryptedUser.data.map((user) => ({
157+
...user,
158+
testRunId: TEST_RUN_ID,
159+
}))
160+
151161
const insertedUsers = await db
152162
.insert(drizzleUsersTable)
153-
.values(encryptedUser.data)
163+
.values(dataWithTestRunId)
154164
.returning({
155165
id: drizzleUsersTable.id,
156166
email: drizzleUsersTable.email,
@@ -164,11 +174,10 @@ beforeAll(async () => {
164174
}, 60000)
165175

166176
afterAll(async () => {
167-
// Clean up test data using Drizzle
168-
if (testData.length > 0) {
169-
const ids = testData.map((d) => d.id)
170-
await db.delete(drizzleUsersTable).where(inArray(drizzleUsersTable.id, ids))
171-
}
177+
// Clean up test data using test_run_id for reliable isolation
178+
await db
179+
.delete(drizzleUsersTable)
180+
.where(eq(drizzleUsersTable.testRunId, TEST_RUN_ID))
172181
}, 30000)
173182

174183
describe('Drizzle ORM Integration with Protect.js', () => {
@@ -185,7 +194,12 @@ describe('Drizzle ORM Integration with Protect.js', () => {
185194
profile: drizzleUsersTable.profile,
186195
})
187196
.from(drizzleUsersTable)
188-
.where(await protectOps.eq(drizzleUsersTable.email, searchEmail))
197+
.where(
198+
and(
199+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
200+
await protectOps.eq(drizzleUsersTable.email, searchEmail),
201+
),
202+
)
189203

190204
expect(results).toHaveLength(1)
191205

@@ -212,7 +226,12 @@ describe('Drizzle ORM Integration with Protect.js', () => {
212226
profile: drizzleUsersTable.profile,
213227
})
214228
.from(drizzleUsersTable)
215-
.where(await protectOps.ilike(drizzleUsersTable.email, searchText))
229+
.where(
230+
and(
231+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
232+
await protectOps.ilike(drizzleUsersTable.email, searchText),
233+
),
234+
)
216235

217236
// Should find users with 'smith' in their email
218237
expect(results.length).toBeGreaterThan(0)
@@ -251,7 +270,12 @@ describe('Drizzle ORM Integration with Protect.js', () => {
251270
profile: drizzleUsersTable.profile,
252271
})
253272
.from(drizzleUsersTable)
254-
.where(await protectOps.gte(drizzleUsersTable.age, minAge))
273+
.where(
274+
and(
275+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
276+
await protectOps.gte(drizzleUsersTable.age, minAge),
277+
),
278+
)
255279

256280
// Should find users with age >= 28
257281
expect(results.length).toBeGreaterThan(0)
@@ -291,6 +315,7 @@ describe('Drizzle ORM Integration with Protect.js', () => {
291315
profile: drizzleUsersTable.profile,
292316
})
293317
.from(drizzleUsersTable)
318+
.where(eq(drizzleUsersTable.testRunId, TEST_RUN_ID))
294319
.orderBy(protectOps.asc(drizzleUsersTable.age))
295320

296321
const results = await a
@@ -336,6 +361,7 @@ describe('Drizzle ORM Integration with Protect.js', () => {
336361
.from(drizzleUsersTable)
337362
.where(
338363
await protectOps.and(
364+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
339365
protectOps.gte(drizzleUsersTable.age, minAge),
340366
protectOps.lte(drizzleUsersTable.age, maxAge),
341367
protectOps.ilike(drizzleUsersTable.email, searchText),
@@ -386,10 +412,13 @@ describe('Drizzle ORM Integration with Protect.js', () => {
386412
})
387413
.from(drizzleUsersTable)
388414
.where(
389-
await protectOps.or(
390-
protectOps.eq(drizzleUsersTable.email, targetEmails[0]),
391-
protectOps.eq(drizzleUsersTable.email, targetEmails[1]),
392-
eq(drizzleUsersTable.id, fallbackId),
415+
await protectOps.and(
416+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
417+
protectOps.or(
418+
protectOps.eq(drizzleUsersTable.email, targetEmails[0]),
419+
protectOps.eq(drizzleUsersTable.email, targetEmails[1]),
420+
eq(drizzleUsersTable.id, fallbackId),
421+
),
393422
),
394423
)
395424

@@ -422,6 +451,7 @@ describe('Drizzle ORM Integration with Protect.js', () => {
422451
profile: drizzleUsersTable.profile,
423452
})
424453
.from(drizzleUsersTable)
454+
.where(eq(drizzleUsersTable.testRunId, TEST_RUN_ID))
425455
.limit(1)
426456

427457
if (!results[0]) {
@@ -457,7 +487,12 @@ describe('Drizzle ORM Integration with Protect.js', () => {
457487
profile: drizzleUsersTable.profile,
458488
})
459489
.from(drizzleUsersTable)
460-
.where(await protectOps.inArray(drizzleUsersTable.email, searchEmails))
490+
.where(
491+
and(
492+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
493+
await protectOps.inArray(drizzleUsersTable.email, searchEmails),
494+
),
495+
)
461496

462497
// Should find 2 users
463498
expect(results.length).toBe(2)
@@ -492,7 +527,12 @@ describe('Drizzle ORM Integration with Protect.js', () => {
492527
profile: drizzleUsersTable.profile,
493528
})
494529
.from(drizzleUsersTable)
495-
.where(await protectOps.between(drizzleUsersTable.age, minAge, maxAge))
530+
.where(
531+
and(
532+
eq(drizzleUsersTable.testRunId, TEST_RUN_ID),
533+
await protectOps.between(drizzleUsersTable.age, minAge, maxAge),
534+
),
535+
)
496536

497537
// Should find users with age between 25 and 30
498538
expect(results.length).toBeGreaterThan(0)

packages/drizzle/src/pg/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const columnConfigMap = new Map<
3636

3737
/**
3838
* Creates an encrypted column type for Drizzle ORM with configurable searchable encryption options.
39-
*
39+
*
4040
* When data is encrypted, the actual stored value is an [EQL v2](/docs/reference/eql) encrypted composite type which includes any searchable encryption indexes defined for the column.
4141
* Importantly, the original data type is not known until it is decrypted. Therefore, this function allows specifying
4242
* the original data type via the `dataType` option in the configuration.
@@ -46,23 +46,23 @@ const columnConfigMap = new Map<
4646
* @param name - The column name in the database
4747
* @param config - Optional configuration for data type and searchable encryption indexes
4848
* @returns A Drizzle column type that can be used in pgTable definitions
49-
*
49+
*
5050
* ## Searchable Encryption Options
51-
*
51+
*
5252
* - `dataType`: Specifies the original data type of the column (e.g., 'string', 'number', 'json'). Default is 'string'.
5353
* - `freeTextSearch`: Enables free text search index. Can be a boolean for default options, or an object for custom configuration.
5454
* - `equality`: Enables equality index. Can be a boolean for default options, or an array of token filters.
5555
* - `orderAndRange`: Enables order and range index for sorting and range queries.
56-
*
56+
*
5757
* See {@link EncryptedColumnConfig}.
5858
*
5959
* @example
6060
* Defining a drizzle table schema for postgres table with encrypted columns.
61-
*
61+
*
6262
* ```typescript
6363
* import { pgTable, integer, timestamp } from 'drizzle-orm/pg-core'
6464
* import { encryptedType } from '@cipherstash/drizzle/pg'
65-
*
65+
*
6666
* const users = pgTable('users', {
6767
* email: encryptedType('email', {
6868
* freeTextSearch: true,

packages/drizzle/src/pg/operators.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
892892
/**
893893
* Equality operator - encrypts value for encrypted columns.
894894
* Requires either `equality` or `orderAndRange` to be set on {@link EncryptedColumnConfig}.
895-
*
895+
*
896896
* @example
897897
* Select users with a specific email address.
898898
* ```ts
@@ -905,7 +905,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
905905
/**
906906
* Not equal operator - encrypts value for encrypted columns.
907907
* Requires either `equality` or `orderAndRange` to be set on {@link EncryptedColumnConfig}.
908-
*
908+
*
909909
* @example
910910
* Select users whose email address is not a specific value.
911911
* ```ts
@@ -918,7 +918,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
918918
/**
919919
* Greater than operator for encrypted columns with ORE index.
920920
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
921-
*
921+
*
922922
* @example
923923
* Select users older than a specific age.
924924
* ```ts
@@ -931,7 +931,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
931931
/**
932932
* Greater than or equal operator for encrypted columns with ORE index.
933933
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
934-
*
934+
*
935935
* @example
936936
* Select users older than or equal to a specific age.
937937
* ```ts
@@ -944,7 +944,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
944944
/**
945945
* Less than operator for encrypted columns with ORE index.
946946
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
947-
*
947+
*
948948
* @example
949949
* Select users younger than a specific age.
950950
* ```ts
@@ -957,7 +957,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
957957
/**
958958
* Less than or equal operator for encrypted columns with ORE index.
959959
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
960-
*
960+
*
961961
* @example
962962
* Select users younger than or equal to a specific age.
963963
* ```ts
@@ -966,11 +966,11 @@ export function createProtectOperators(protectClient: ProtectClient): {
966966
* ```
967967
*/
968968
lte: (left: SQLWrapper, right: unknown) => Promise<SQL> | SQL
969-
969+
970970
/**
971971
* Between operator for encrypted columns with ORE index.
972972
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
973-
*
973+
*
974974
* @example
975975
* Select users within a specific age range.
976976
* ```ts
@@ -983,7 +983,7 @@ export function createProtectOperators(protectClient: ProtectClient): {
983983
/**
984984
* Not between operator for encrypted columns with ORE index.
985985
* Requires `orderAndRange` to be set on {@link EncryptedColumnConfig}.
986-
*
986+
*
987987
* @example
988988
* Select users outside a specific age range.
989989
* ```ts
@@ -1000,28 +1000,28 @@ export function createProtectOperators(protectClient: ProtectClient): {
10001000
/**
10011001
* Like operator for encrypted columns with free text search.
10021002
* Requires `freeTextSearch` to be set on {@link EncryptedColumnConfig}.
1003-
*
1003+
*
10041004
* > [!IMPORTANT]
10051005
* > Case sensitivity on encrypted columns depends on the {@link EncryptedColumnConfig}.
10061006
* > Ensure that the column is configured for case-insensitive search if needed.
1007-
*
1007+
*
10081008
* @example
10091009
* Select users with email addresses matching a pattern.
10101010
* ```ts
10111011
* const condition = await protectOps.like(usersTable.email, '%@example.com')
10121012
* const results = await db.select().from(usersTable).where(condition)
1013-
* ```
1013+
* ```
10141014
*/
10151015
like: (left: SQLWrapper, right: unknown) => Promise<SQL> | SQL
10161016

10171017
/**
10181018
* ILike operator for encrypted columns with free text search.
10191019
* Requires `freeTextSearch` to be set on {@link EncryptedColumnConfig}.
1020-
*
1020+
*
10211021
* > [!IMPORTANT]
10221022
* > Case sensitivity on encrypted columns depends on the {@link EncryptedColumnConfig}.
10231023
* > Ensure that the column is configured for case-insensitive search if needed.
1024-
*
1024+
*
10251025
* @example
10261026
* Select users with email addresses matching a pattern (case-insensitive).
10271027
* ```ts

0 commit comments

Comments
 (0)