-
Notifications
You must be signed in to change notification settings - Fork 79
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: auto increment support (#2883)
* chore(graphql-default-value-transformer): tidy tests * test(graphql-default-value-transformer): add unit tests for auto increment support * feat: 🎸 utils to detect Postgres datasource * feat: 🎸 support auto increment Implements support for auto increment (serial) fields from Postgres datasources. Such fields are denoted by an empty `@default` applied to an `Int` field. * test(graphql-default-value-transformer): pk can be auto increment * test(graphql-default-value-transformer): auto-increment crud e2e * chore: describe test purpose * chore: removing logging * chore: describe why invalid cases are invalid * chore: remove unecessary e2e test case * chore: test messaging clarity * chore: type safety * chore: alphabetize list * chore: type of return value asserts against string Co-authored-by: Tim Schmelter <schmelte+github@amazon.com> * chore: test ensures customers can insert to serial fields with custom values * chore: verify that @default(value) works on mysql * chore: remove unecessary ssm test case * chore: update branch from main * test: value cannot be null on ddb --------- Co-authored-by: Tim Schmelter <schmelte+github@amazon.com>
- Loading branch information
Showing
16 changed files
with
963 additions
and
240 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
55 changes: 55 additions & 0 deletions
55
packages/amplify-graphql-api-construct-tests/src/__tests__/sql-pg-auto-increment.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,55 @@ | ||
import generator from 'generate-password'; | ||
import { getResourceNamesForStrategyName, ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; | ||
import { getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; | ||
import { SqlDatatabaseController } from '../sql-datatabase-controller'; | ||
import { DURATION_1_HOUR } from '../utils/duration-constants'; | ||
import { testGraphQLAPIAutoIncrement } from '../sql-tests-common/sql-models-auto-increment'; | ||
|
||
jest.setTimeout(DURATION_1_HOUR); | ||
|
||
describe('CDK GraphQL Transformer deployments with Postgres SQL datasources', () => { | ||
const projFolderName = 'pgmodels'; | ||
|
||
// sufficient password length that meets the requirements for RDS cluster/instance | ||
const [username, password, identifier] = generator.generateMultiple(3, { length: 11 }); | ||
const region = process.env.CLI_REGION ?? 'us-west-2'; | ||
const engine = 'postgres'; | ||
|
||
const databaseController: SqlDatatabaseController = new SqlDatatabaseController( | ||
[ | ||
`CREATE TABLE "${getRDSTableNamePrefix()}coffee_queue" ("orderNumber" SERIAL PRIMARY KEY, "order" VARCHAR(256) NOT NULL, "customer" VARCHAR(256))`, | ||
], | ||
{ | ||
identifier, | ||
engine, | ||
username, | ||
password, | ||
region, | ||
}, | ||
); | ||
|
||
const strategyName = `${engine}DBStrategy`; | ||
const resourceNames = getResourceNamesForStrategyName(strategyName); | ||
|
||
beforeAll(async () => { | ||
await databaseController.setupDatabase(); | ||
}); | ||
|
||
afterAll(async () => { | ||
await databaseController.cleanupDatabase(); | ||
}); | ||
|
||
const constructTestOptions = (connectionConfigName: string) => ({ | ||
projFolderName, | ||
region, | ||
connectionConfigName, | ||
dbController: databaseController, | ||
resourceNames, | ||
}); | ||
|
||
testGraphQLAPIAutoIncrement( | ||
constructTestOptions('connectionUri'), | ||
'creates a GraphQL API from SQL-based models using Connection String SSM parameter', | ||
ImportedRDSType.POSTGRESQL, | ||
); | ||
}); |
7 changes: 7 additions & 0 deletions
7
...-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/field-map.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,7 @@ | ||
import { FieldMap } from '../../../utils/sql-crudl-helper'; | ||
|
||
export const coffeeQueueFieldMap: FieldMap = { | ||
orderNumber: true, | ||
order: true, | ||
customer: true, | ||
}; |
5 changes: 5 additions & 0 deletions
5
...raphql-api-construct-tests/src/sql-tests-common/schemas/sql-auto-increment/schema.graphql
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,5 @@ | ||
type CoffeeQueue @model @refersTo(name: "e2e_test_coffee_queue") { | ||
orderNumber: Int! @primaryKey @default | ||
order: String! | ||
customer: String | ||
} |
2 changes: 1 addition & 1 deletion
2
...mplify-graphql-api-construct-tests/src/sql-tests-common/schemas/sql-models/schema.graphql
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
174 changes: 174 additions & 0 deletions
174
...ges/amplify-graphql-api-construct-tests/src/sql-tests-common/sql-models-auto-increment.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,174 @@ | ||
import * as path from 'path'; | ||
import * as fs from 'fs-extra'; | ||
import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core'; | ||
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync'; | ||
import { createNewProjectDir, deleteProjectDir, getRDSTableNamePrefix } from 'amplify-category-api-e2e-core'; | ||
import { initCDKProject, cdkDeploy, cdkDestroy } from '../commands'; | ||
import { SqlDatatabaseController } from '../sql-datatabase-controller'; | ||
import { CRUDLHelper } from '../utils/sql-crudl-helper'; | ||
import { ONE_MINUTE } from '../utils/duration-constants'; | ||
import { coffeeQueueFieldMap } from './schemas/sql-auto-increment/field-map'; | ||
|
||
export const testGraphQLAPIAutoIncrement = ( | ||
options: { | ||
projFolderName: string; | ||
region: string; | ||
connectionConfigName: string; | ||
dbController: SqlDatatabaseController; | ||
resourceNames: { sqlLambdaAliasName: string }; | ||
}, | ||
testBlockDescription: string, | ||
engine: ImportedRDSType, | ||
): void => { | ||
describe(`${testBlockDescription} - ${engine}`, () => { | ||
// In particular, we want to verify that the new CREATE operation | ||
// is allowed to omit the primary key field, and that the primary key | ||
// we get back is the correct, db generated value. | ||
// NOTE: Expects underlying orderNumber column to be a serial primary key in Postgres table | ||
const schemaPath = path.resolve(path.join(__dirname, '..', 'sql-tests-common', 'schemas', 'sql-auto-increment', 'schema.graphql')); | ||
const schemaConfigString = fs.readFileSync(schemaPath).toString(); | ||
const { projFolderName, region, connectionConfigName, dbController } = options; | ||
const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models')); | ||
|
||
let projRoot: string; | ||
let name: string; | ||
let outputs: Promise<any>; | ||
let coffeeQueueTableCRUDLHelper: CRUDLHelper; | ||
|
||
beforeAll(async () => { | ||
projRoot = await createNewProjectDir(projFolderName); | ||
name = await initCDKProject(projRoot, templatePath); | ||
dbController.writeDbDetails(projRoot, connectionConfigName, schemaConfigString); | ||
outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE }); | ||
const { awsAppsyncApiEndpoint: apiEndpoint, awsAppsyncApiKey: apiKey } = outputs[name]; | ||
|
||
const appSyncClient = new AWSAppSyncClient({ | ||
url: apiEndpoint, | ||
region, | ||
disableOffline: true, | ||
auth: { | ||
type: AUTH_TYPE.API_KEY, | ||
apiKey, | ||
}, | ||
}); | ||
|
||
coffeeQueueTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'CoffeeQueue', 'CoffeeQueues', coffeeQueueFieldMap); | ||
}); | ||
|
||
afterAll(async () => { | ||
try { | ||
await cdkDestroy(projRoot, '--all'); | ||
await dbController.clearDatabase(); | ||
} catch (err) { | ||
console.log(`Error invoking 'cdk destroy': ${err}`); | ||
} | ||
|
||
deleteProjectDir(projRoot); | ||
}); | ||
|
||
test(`check CRUDL on coffee queue table with auto increment primary key - ${engine}`, async () => { | ||
// Order Coffee Mutation | ||
const createCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.create({ customer: 'petesv', order: 'cold brew' }); | ||
|
||
expect(createCoffeeOrder1).toBeDefined(); | ||
expect(createCoffeeOrder1.orderNumber).toBeDefined(); | ||
expect(createCoffeeOrder1.customer).toEqual('petesv'); | ||
expect(createCoffeeOrder1.order).toEqual('cold brew'); | ||
|
||
// Get Todo Query | ||
const getCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); | ||
|
||
expect(getCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); | ||
expect(getCoffeeOrder1.customer).toEqual(createCoffeeOrder1.customer); | ||
|
||
// Update Todo Mutation | ||
const updateCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.update({ | ||
orderNumber: createCoffeeOrder1.orderNumber, | ||
customer: 'petesv', | ||
order: 'hot brew', | ||
}); | ||
|
||
expect(updateCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); | ||
expect(updateCoffeeOrder1.order).toEqual('hot brew'); | ||
|
||
// Get Todo Query after update | ||
const getUpdatedCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); | ||
|
||
expect(getUpdatedCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); | ||
expect(getUpdatedCoffeeOrder1.order).toEqual('hot brew'); | ||
|
||
// List Todo Query & Create with custom SERIAL field value | ||
const customOrderNumber = 42; | ||
const createCofffeeOrder2 = await coffeeQueueTableCRUDLHelper.create({ orderNumber: customOrderNumber, order: 'latte' }); | ||
expect(createCofffeeOrder2.orderNumber).toEqual(customOrderNumber); | ||
|
||
const listTodo = await coffeeQueueTableCRUDLHelper.list(); | ||
expect(listTodo.items.length).toEqual(2); | ||
expect(listTodo.items).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
orderNumber: getUpdatedCoffeeOrder1.orderNumber, | ||
order: 'hot brew', | ||
}), | ||
expect.objectContaining({ | ||
orderNumber: createCofffeeOrder2.orderNumber, | ||
order: 'latte', | ||
}), | ||
]), | ||
); | ||
|
||
// Delete Todo Mutation | ||
const deleteCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.delete({ orderNumber: createCoffeeOrder1.orderNumber }); | ||
|
||
expect(deleteCoffeeOrder1.orderNumber).toEqual(createCoffeeOrder1.orderNumber); | ||
expect(deleteCoffeeOrder1.order).toEqual('hot brew'); | ||
|
||
const getDeletedCoffeeOrder1 = await coffeeQueueTableCRUDLHelper.get({ orderNumber: createCoffeeOrder1.orderNumber }); | ||
|
||
expect(getDeletedCoffeeOrder1).toBeNull(); | ||
|
||
// List Todo Query after delete | ||
const listCoffeeOrdersAfterDelete = await coffeeQueueTableCRUDLHelper.list(); | ||
|
||
expect(listCoffeeOrdersAfterDelete.items.length).toEqual(1); | ||
expect(listCoffeeOrdersAfterDelete.items).toEqual( | ||
expect.arrayContaining([ | ||
expect.objectContaining({ | ||
orderNumber: createCofffeeOrder2.orderNumber, | ||
order: 'latte', | ||
}), | ||
]), | ||
); | ||
|
||
// Check invalid CRUD operation returns generic error message | ||
const createTodo6 = await coffeeQueueTableCRUDLHelper.create({ order: 'mocha' }); | ||
|
||
try { | ||
// Invalid because the pk (orderNumber) already exists | ||
await coffeeQueueTableCRUDLHelper.create({ orderNumber: createTodo6.orderNumber, order: 'americano' }); | ||
} catch (error) { | ||
coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); | ||
} | ||
|
||
const biggerThanAnyExistingOrderNumber = 99999999; | ||
|
||
try { | ||
await coffeeQueueTableCRUDLHelper.get({ orderNumber: biggerThanAnyExistingOrderNumber }); | ||
} catch (error) { | ||
coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); | ||
} | ||
|
||
try { | ||
await coffeeQueueTableCRUDLHelper.update({ orderNumber: biggerThanAnyExistingOrderNumber, order: 'cortado' }); | ||
} catch (error) { | ||
coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); | ||
} | ||
|
||
try { | ||
await coffeeQueueTableCRUDLHelper.delete({ orderNumber: biggerThanAnyExistingOrderNumber }); | ||
} catch (error) { | ||
coffeeQueueTableCRUDLHelper.checkGenericError(error?.message); | ||
} | ||
}); | ||
}); | ||
}; |
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.