-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
404 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
...atabase-adapter-knex/src/__integration-tests__/PostgresEntityQueryContextProvider-test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import { ViewerContext } from '@expo/entity'; | ||
import { knex, Knex } from 'knex'; | ||
import nullthrows from 'nullthrows'; | ||
|
||
import PostgresEntityQueryContextProvider from '../PostgresEntityQueryContextProvider'; | ||
import PostgresUniqueTestEntity from '../testfixtures/PostgresUniqueTestEntity'; | ||
import { createKnexIntegrationTestEntityCompanionProvider } from '../testfixtures/createKnexIntegrationTestEntityCompanionProvider'; | ||
|
||
describe(PostgresEntityQueryContextProvider, () => { | ||
let knexInstance: Knex; | ||
|
||
beforeAll(() => { | ||
knexInstance = knex({ | ||
client: 'pg', | ||
connection: { | ||
user: nullthrows(process.env['PGUSER']), | ||
password: nullthrows(process.env['PGPASSWORD']), | ||
host: 'localhost', | ||
port: parseInt(nullthrows(process.env['PGPORT']), 10), | ||
database: nullthrows(process.env['PGDATABASE']), | ||
}, | ||
}); | ||
}); | ||
|
||
beforeEach(async () => { | ||
await PostgresUniqueTestEntity.createOrTruncatePostgresTable(knexInstance); | ||
}); | ||
|
||
afterAll(async () => { | ||
await PostgresUniqueTestEntity.dropPostgresTable(knexInstance); | ||
await knexInstance.destroy(); | ||
}); | ||
|
||
it('supports nested transactions', async () => { | ||
const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); | ||
|
||
await PostgresUniqueTestEntity.creator(vc1).setField('name', 'unique').enforceCreateAsync(); | ||
|
||
const id = ( | ||
await PostgresUniqueTestEntity.creator(vc1).setField('name', 'wat').enforceCreateAsync() | ||
).getID(); | ||
|
||
await vc1.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', async (queryContext) => { | ||
const entity = await PostgresUniqueTestEntity.loader(vc1, queryContext) | ||
.enforcing() | ||
.loadByIDAsync(id); | ||
await PostgresUniqueTestEntity.updater(entity, queryContext) | ||
.setField('name', 'wat2') | ||
.enforceUpdateAsync(); | ||
|
||
// ensure the outer transaction is not aborted due to postgres error in inner transaction, | ||
// in this case the error triggered is a unique constraint violation | ||
try { | ||
await queryContext.runInNestedTransactionAsync(async (innerQueryContext) => { | ||
const entity = await PostgresUniqueTestEntity.loader(vc1, innerQueryContext) | ||
.enforcing() | ||
.loadByIDAsync(id); | ||
await PostgresUniqueTestEntity.updater(entity, innerQueryContext) | ||
.setField('name', 'unique') | ||
.enforceUpdateAsync(); | ||
}); | ||
} catch {} | ||
|
||
const entity2 = await PostgresUniqueTestEntity.loader(vc1, queryContext) | ||
.enforcing() | ||
.loadByIDAsync(id); | ||
await PostgresUniqueTestEntity.updater(entity2, queryContext) | ||
.setField('name', 'wat3') | ||
.enforceUpdateAsync(); | ||
}); | ||
|
||
const entityLoaded = await PostgresUniqueTestEntity.loader(vc1).enforcing().loadByIDAsync(id); | ||
expect(entityLoaded.getField('name')).toEqual('wat3'); | ||
}); | ||
|
||
it('supports multi-nested transactions', async () => { | ||
const vc1 = new ViewerContext(createKnexIntegrationTestEntityCompanionProvider(knexInstance)); | ||
|
||
const id = ( | ||
await PostgresUniqueTestEntity.creator(vc1).setField('name', 'wat').enforceCreateAsync() | ||
).getID(); | ||
|
||
await vc1.runInTransactionForDatabaseAdaptorFlavorAsync('postgres', async (queryContext) => { | ||
await queryContext.runInNestedTransactionAsync(async (innerQueryContext) => { | ||
await innerQueryContext.runInNestedTransactionAsync(async (innerQueryContex2) => { | ||
await innerQueryContex2.runInNestedTransactionAsync(async (innerQueryContex3) => { | ||
const entity = await PostgresUniqueTestEntity.loader(vc1, innerQueryContex3) | ||
.enforcing() | ||
.loadByIDAsync(id); | ||
await PostgresUniqueTestEntity.updater(entity, innerQueryContex3) | ||
.setField('name', 'wat3') | ||
.enforceUpdateAsync(); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
const entityLoaded = await PostgresUniqueTestEntity.loader(vc1).enforcing().loadByIDAsync(id); | ||
expect(entityLoaded.getField('name')).toEqual('wat3'); | ||
}); | ||
}); |
117 changes: 117 additions & 0 deletions
117
packages/entity-database-adapter-knex/src/testfixtures/PostgresUniqueTestEntity.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import { | ||
AlwaysAllowPrivacyPolicyRule, | ||
EntityPrivacyPolicy, | ||
ViewerContext, | ||
UUIDField, | ||
StringField, | ||
EntityConfiguration, | ||
EntityCompanionDefinition, | ||
Entity, | ||
} from '@expo/entity'; | ||
import { Knex } from 'knex'; | ||
|
||
type PostgresUniqueTestEntityFields = { | ||
id: string; | ||
name: string | null; | ||
}; | ||
|
||
export default class PostgresUniqueTestEntity extends Entity< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext | ||
> { | ||
static getCompanionDefinition(): EntityCompanionDefinition< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity, | ||
PostgresUniqueTestEntityPrivacyPolicy | ||
> { | ||
return postgresTestEntityCompanionDefinition; | ||
} | ||
|
||
public static async createOrTruncatePostgresTable(knex: Knex): Promise<void> { | ||
await knex.raw('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"'); // for uuid_generate_v4() | ||
|
||
const tableName = this.getCompanionDefinition().entityConfiguration.tableName; | ||
const hasTable = await knex.schema.hasTable(tableName); | ||
if (!hasTable) { | ||
await knex.schema.createTable(tableName, (table) => { | ||
table.uuid('id').defaultTo(knex.raw('uuid_generate_v4()')).primary(); | ||
table.string('name').unique(); | ||
}); | ||
} | ||
await knex.into(tableName).truncate(); | ||
} | ||
|
||
public static async dropPostgresTable(knex: Knex): Promise<void> { | ||
const tableName = this.getCompanionDefinition().entityConfiguration.tableName; | ||
const hasTable = await knex.schema.hasTable(tableName); | ||
if (hasTable) { | ||
await knex.schema.dropTable(tableName); | ||
} | ||
} | ||
} | ||
|
||
class PostgresUniqueTestEntityPrivacyPolicy extends EntityPrivacyPolicy< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity | ||
> { | ||
protected override readonly createRules = [ | ||
new AlwaysAllowPrivacyPolicyRule< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity | ||
>(), | ||
]; | ||
protected override readonly readRules = [ | ||
new AlwaysAllowPrivacyPolicyRule< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity | ||
>(), | ||
]; | ||
protected override readonly updateRules = [ | ||
new AlwaysAllowPrivacyPolicyRule< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity | ||
>(), | ||
]; | ||
protected override readonly deleteRules = [ | ||
new AlwaysAllowPrivacyPolicyRule< | ||
PostgresUniqueTestEntityFields, | ||
string, | ||
ViewerContext, | ||
PostgresUniqueTestEntity | ||
>(), | ||
]; | ||
} | ||
|
||
export const postgresTestEntityConfiguration = | ||
new EntityConfiguration<PostgresUniqueTestEntityFields>({ | ||
idField: 'id', | ||
tableName: 'postgres_test_entities', | ||
schema: { | ||
id: new UUIDField({ | ||
columnName: 'id', | ||
cache: true, | ||
}), | ||
name: new StringField({ | ||
columnName: 'name', | ||
}), | ||
}, | ||
databaseAdapterFlavor: 'postgres', | ||
cacheAdapterFlavor: 'redis', | ||
}); | ||
|
||
const postgresTestEntityCompanionDefinition = new EntityCompanionDefinition({ | ||
entityClass: PostgresUniqueTestEntity, | ||
entityConfiguration: postgresTestEntityConfiguration, | ||
privacyPolicyClass: PostgresUniqueTestEntityPrivacyPolicy, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.