Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: migrate pg array objects e2e test in gen2 cdk #2906

Merged
merged 12 commits into from
Oct 2, 2024
Merged
134 changes: 72 additions & 62 deletions codebuild_specs/e2e_workflow.yml

Large diffs are not rendered by default.

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 { testGraphQLAPIArrayAndObjects } from '../sql-tests-common/sql-array-objects';

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()}contact" ("id" INT PRIMARY KEY, "firstname" VARCHAR(20), "lastname" VARCHAR(50), "tags" VARCHAR[], "address" JSON)`,
],
{
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,
});

testGraphQLAPIArrayAndObjects(
constructTestOptions('connectionUri'),
'RDS Postgres Model Directive using Connection String SSM parameter',
ImportedRDSType.POSTGRESQL,
);
Siqi-Shan marked this conversation as resolved.
Show resolved Hide resolved
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FieldMap } from '../../../utils/sql-crudl-helper';

export const contactFieldMap: FieldMap = {
id: true,
firstname: true,
lastname: true,
tags: true,
address: {
city: true,
state: true,
street: true,
zip: true,
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
type Contact @refersTo(name: "e2e_test_contact") @model {
id: Int! @primaryKey
firstname: String
lastname: String
tags: [String]
address: ContactAddress
}

type ContactAddress {
city: String!
state: String!
street: String!
zip: String!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { FieldMap } from '../../../utils/sql-crudl-helper';

export const toDoFieldMap: FieldMap = {
id: true,
description: true,
};

export const studentFieldMap: FieldMap = {
studentId: true,
classId: true,
firstName: true,
lastName: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type Todo @model @refersTo(name: "e2e_test_todos") {
id: ID! @primaryKey
description: String!
}
type Student @model @refersTo(name: "e2e_test_students") {
studentId: Int! @primaryKey(sortKeyFields: ["classId"])
classId: String!
firstName: String
lastName: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { LambdaClient, GetProvisionedConcurrencyConfigCommand } from '@aws-sdk/client-lambda';
import { ImportedRDSType } from '@aws-amplify/graphql-transformer-core';
import AWSAppSyncClient, { AUTH_TYPE } from 'aws-appsync';
import { createNewProjectDir, deleteProjectDir } 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 { contactFieldMap } from './schemas/sql-array-objects/field-map';
import { ONE_MINUTE } from '../utils/duration-constants';

export const testGraphQLAPIArrayAndObjects = (
options: {
projFolderName: string;
region: string;
connectionConfigName: string;
dbController: SqlDatatabaseController;
resourceNames: { sqlLambdaAliasName: string };
},
testBlockDescription: string,
engine: ImportedRDSType,
): void => {
describe(`${testBlockDescription} - ${engine}`, () => {
let projRoot;
let region, lambdaFunctionName, lambdaAliasName;

let dbController: SqlDatatabaseController;
let contactTableCRUDLHelper: CRUDLHelper;

beforeAll(async () => {
({
region,
dbController,
resourceNames: { sqlLambdaAliasName: lambdaAliasName },
} = options);
const { projFolderName, connectionConfigName } = options;

const templatePath = path.resolve(path.join(__dirname, '..', '__tests__', 'backends', 'sql-models'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now or in the next PR: Can we migrate this test to use the configurable stack so we can (eventually) remove the sql-models stack? Eventually I'd like us to be using just one stack to reduce the number of test fixtures we have to maintain.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thoughts of using an universal configurable-stack:

  • It's totally feasible to have all tests depends on a single stack with every test defines the specific parameter through various command json files, but more time-consuming than have individual CDK per test;
  • This requires expansion of current configurable stack functionalities, including support of other authorization modes like OIDC, IAM, etc., and the shift to AmplifyAuth construct from individual UserPool. Plus, AmplifyAuth provides a convenient way of managing auth-related resources;
  • Another way of supporting different resources requirements of tests is to through more helper functions instead of a centralized provider like AmplifyAuth based on command files parameters, e.g. when the test is about IAM, then a helper called createIAMIdentityPool is applied to provision the resource;
  • Reducing the number of CDK related files to one stack will benefit maintaining the fixtures, and may need breaking and larger scale changes in current configurable-stack, which may lead to redesign of some of the tests' resources provision.

I could try to utilize the configurable-stack to provision the resources, and figure if OIDC could be supported as well for better management over the test fixtures.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your thoughts on this. For now, let's defer any move to configurable-stack, and we can consider a holistic design later.

const schemaPath = path.resolve(path.join(__dirname, '..', 'sql-tests-common', 'schemas', 'sql-array-objects', 'schema.graphql'));
const schemaConfigString = fs.readFileSync(schemaPath).toString();

projRoot = await createNewProjectDir(projFolderName);
const name = await initCDKProject(projRoot, templatePath);
dbController.writeDbDetails(projRoot, connectionConfigName, schemaConfigString);
const outputs = await cdkDeploy(projRoot, '--all', { postDeployWaitMs: ONE_MINUTE });
const { awsAppsyncApiEndpoint: apiEndpoint, awsAppsyncApiKey: apiKey } = outputs[name];
lambdaFunctionName = outputs[name].SQLFunctionName;

const appSyncClient = new AWSAppSyncClient({
url: apiEndpoint,
region,
disableOffline: true,
auth: {
type: AUTH_TYPE.API_KEY,
apiKey,
},
});

contactTableCRUDLHelper = new CRUDLHelper(appSyncClient, 'Contact', 'Contacts', contactFieldMap);
});

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 contact table with array and objects - ${engine}`, async () => {
// Create Contact Mutation
const contact1 = await contactTableCRUDLHelper.create({
id: 1,
firstname: 'David',
lastname: 'Smith',
tags: ['tag1', 'tag2'],
address: {
city: 'Seattle',
state: 'WA',
street: '123 Main St',
zip: '98115',
},
});
const contact2 = await contactTableCRUDLHelper.create({
id: 2,
firstname: 'Chris',
lastname: 'Sundersingh',
tags: ['tag3', 'tag4'],
address: {
city: 'Seattle',
state: 'WA',
street: '456 Another St',
zip: '98119',
},
});

expect(contact1).toBeDefined();
expect(contact1.id).toEqual(1);
expect(contact1.firstname).toEqual('David');
expect(contact1.lastname).toEqual('Smith');
expect(contact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2']));
expect(contact1.address).toEqual(
expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '123 Main St',
zip: '98115',
}),
);

// Get Contact Query
const getContact1 = await contactTableCRUDLHelper.get({ id: contact1.id });

expect(getContact1.id).toEqual(contact1.id);
expect(getContact1.firstname).toEqual('David');
expect(getContact1.lastname).toEqual('Smith');
expect(getContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2']));
expect(getContact1.address).toEqual(
expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '123 Main St',
zip: '98115',
}),
);

// Update Contact Query
const updateContact1 = await contactTableCRUDLHelper.update({
id: contact1.id,
firstname: 'David',
lastname: 'Jones',
tags: ['tag1', 'tag2', 'tag3'],
address: {
city: 'Seattle',
state: 'WA',
street: '12345 Main St',
zip: '98110',
},
});

expect(updateContact1.id).toEqual(contact1.id);
expect(updateContact1.firstname).toEqual('David');
expect(updateContact1.lastname).toEqual('Jones');
expect(updateContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3']));
expect(updateContact1.address).toEqual(
expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '12345 Main St',
zip: '98110',
}),
);

// Get Contact Query after update
const getUpdatedContact1 = await contactTableCRUDLHelper.get({ id: contact1.id });

expect(getUpdatedContact1.id).toEqual(contact1.id);
expect(getUpdatedContact1.firstname).toEqual('David');
expect(getUpdatedContact1.lastname).toEqual('Jones');
expect(getUpdatedContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3']));
expect(getUpdatedContact1.address).toEqual(
expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '12345 Main St',
zip: '98110',
}),
);

// List Contact Query
const listContact = await contactTableCRUDLHelper.list();

expect(listContact.items.length).toEqual(2);
expect(listContact.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: contact1.id,
firstname: 'David',
lastname: 'Jones',
tags: expect.arrayContaining(['tag1', 'tag2', 'tag3']),
address: expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '12345 Main St',
zip: '98110',
}),
}),
expect.objectContaining({
id: contact2.id,
firstname: 'Chris',
lastname: 'Sundersingh',
tags: expect.arrayContaining(['tag3', 'tag4']),
address: expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '456 Another St',
zip: '98119',
}),
}),
]),
);

// Delete Contact Mutation
const deleteContact1 = await contactTableCRUDLHelper.delete({ id: contact1.id });

expect(deleteContact1.id).toEqual(contact1.id);
expect(deleteContact1.firstname).toEqual('David');
expect(deleteContact1.lastname).toEqual('Jones');
expect(deleteContact1.tags).toEqual(expect.arrayContaining(['tag1', 'tag2', 'tag3']));
expect(deleteContact1.address).toEqual(
expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '12345 Main St',
zip: '98110',
}),
);

// List Contact Query after delete
const listContactAfterDelete = await contactTableCRUDLHelper.list();

expect(listContactAfterDelete.items.length).toEqual(1);
expect(listContactAfterDelete.items).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: contact2.id,
firstname: 'Chris',
lastname: 'Sundersingh',
tags: expect.arrayContaining(['tag3', 'tag4']),
address: expect.objectContaining({
city: 'Seattle',
state: 'WA',
street: '456 Another St',
zip: '98119',
}),
}),
]),
);
});

test(`check SQL Lambda provisioned concurrency - ${engine}`, async () => {
const client = new LambdaClient({ region });
const command = new GetProvisionedConcurrencyConfigCommand({
FunctionName: lambdaFunctionName,
Qualifier: lambdaAliasName,
});
const response = await client.send(command);
expect(response.RequestedProvisionedConcurrentExecutions).toEqual(2);
});
});
};
Loading
Loading