diff --git a/packages/amplify-data-construct/.jsii b/packages/amplify-data-construct/.jsii index cc81c66d4b..c71fbcada9 100644 --- a/packages/amplify-data-construct/.jsii +++ b/packages/amplify-data-construct/.jsii @@ -6,7 +6,7 @@ ] }, "bundled": { - "@aws-amplify/ai-constructs": "^1.2.4", + "@aws-amplify/ai-constructs": "1.3.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.2.3", @@ -27,7 +27,7 @@ "@aws-amplify/graphql-transformer-core": "3.4.3", "@aws-amplify/graphql-transformer-interfaces": "4.2.6", "@aws-amplify/graphql-validate-transformer": "1.1.3", - "@aws-amplify/platform-core": "^1.0.0", + "@aws-amplify/platform-core": "1.6.5", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -1375,6 +1375,19 @@ } } }, + "aws-cdk-lib.aws_dsql": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.DSQL" + }, + "java": { + "package": "software.amazon.awscdk.services.dsql" + }, + "python": { + "module": "aws_cdk.aws_dsql" + } + } + }, "aws-cdk-lib.aws_dynamodb": { "targets": { "dotnet": { @@ -4109,5 +4122,5 @@ }, "types": {}, "version": "1.16.1", - "fingerprint": "RmejNooIcfMZHBiK6PXB+e2XH6v5bdARzogf0gvsW9Q=" + "fingerprint": "mrjoxD+VARlqcB7yH3pjjXrOgFOAZ2iWFDSt9BEn+9I=" } \ No newline at end of file diff --git a/packages/amplify-data-construct/package.json b/packages/amplify-data-construct/package.json index cb0e65ca38..b52819e17c 100644 --- a/packages/amplify-data-construct/package.json +++ b/packages/amplify-data-construct/package.json @@ -161,7 +161,7 @@ "zod" ], "dependencies": { - "@aws-amplify/ai-constructs": "^1.2.4", + "@aws-amplify/ai-constructs": "1.3.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-api-construct": "1.20.1", @@ -183,7 +183,7 @@ "@aws-amplify/graphql-transformer-core": "3.4.3", "@aws-amplify/graphql-transformer-interfaces": "4.2.6", "@aws-amplify/graphql-validate-transformer": "1.1.3", - "@aws-amplify/platform-core": "^1.0.0", + "@aws-amplify/platform-core": "1.6.5", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", diff --git a/packages/amplify-e2e-core/src/index.ts b/packages/amplify-e2e-core/src/index.ts index 604b074ac0..54a1b95841 100644 --- a/packages/amplify-e2e-core/src/index.ts +++ b/packages/amplify-e2e-core/src/index.ts @@ -72,14 +72,29 @@ export function injectSessionToken(profileName: string) { fs.writeFileSync(pathManager.getAWSCredentialsFilePath(), ini.stringify(credentialsContents)); } +/** + * Execute an `npm install` in the given directory in a child process. + * + * @param cwd + */ export function npmInstall(cwd: string) { spawnSync('npm', ['install'], { cwd }); } +/** + * Run tests in a child process. + * + * @param cwd + */ export function npmTest(cwd: string) { spawnSync('npm', ['test'], { cwd }); } +/** + * Install the global `amplify` command. + * + * @param version + */ export async function installAmplifyCLI(version: string = 'latest') { spawnSync('npm', ['install', '-g', `@aws-amplify/cli@${version}`], { cwd: process.cwd(), @@ -103,6 +118,19 @@ export async function installAmplifyCLI(version: string = 'latest') { console.log('PATH SET:', process.env.AMPLIFY_PATH); } +/** + * Creates a folder in a temp directory for into which app code can be written. Intended for e2e's + * to perform app build-out and deployment. + * + * By default, this also sleeps for a random interval between 0 and 3 minutes. Helps to prevent concurrent + * e2e tests from running `amplify init` and other `amplify` commands concurrently and hitting service limits. + * + * To disable this sleep, run with environment variable `SKIP_CREATE_PROJECT_DIR_INITIAL_DELAY=true`. + * + * @param projectName Any name, ideally one that identifies the test app. E.g., "conversation". + * @param prefix Prefix/Directory under which the project will be created. Defauls to OS temp directory. + * @returns The created directory path. + */ export const createNewProjectDir = async ( projectName: string, prefix = path.join(fs.realpathSync(os.tmpdir()), amplifyTestsDir), @@ -117,10 +145,6 @@ export const createNewProjectDir = async ( fs.ensureDirSync(projectDir); if (!process.env.SKIP_CREATE_PROJECT_DIR_INITIAL_DELAY) { - // createProjectDir(..) is something that nearly every test uses - // Commands like 'init' would collide with each other if they occurred too close to one another. - // Especially for nexpect output waiting - // This makes it a perfect candidate for staggering test start times const initialDelay = Math.floor(Math.random() * 180 * 1000); // between 0 to 3 min console.log(`Waiting for ${initialDelay} ms`); await sleep(initialDelay); @@ -130,6 +154,11 @@ export const createNewProjectDir = async ( return projectDir; }; +/** + * Creates a temp directory in the operating system's default temp location. + * + * @returns The directory path. + */ export const createTempDir = () => { const osTempDir = fs.realpathSync(os.tmpdir()); const tempProjectDir = path.join(osTempDir, amplifyTestsDir, uuid()); diff --git a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/conversation.test.ts b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/conversation.test.ts index 86fa60ad7b..8816bd2ddc 100644 --- a/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/conversation.test.ts +++ b/packages/amplify-graphql-api-construct-tests/src/__tests__/conversations/conversation.test.ts @@ -40,16 +40,25 @@ describe('conversation', () => { let realtimeEndpoint: string; beforeAll(async () => { + console.log('checkpoint a'); projRoot = await createNewProjectDir(projFolderName); + + console.log('checkpoint b'); const { apiEndpoint: graphqlEndpoint, userPoolClientId, userPoolId } = await deployCdk(projRoot); + + console.log('checkpoint c'); apiEndpoint = graphqlEndpoint; realtimeEndpoint = apiEndpoint.replace('appsync-api', 'appsync-realtime-api').replace('https://', 'wss://'); + console.log('checkpoint d'); + const { username: user1Username, password: user1Password } = await createCognitoUser({ region, userPoolId, }); + console.log('checkpoint d'); + const { accessToken: user1AccessToken } = await signInCognitoUser({ username: user1Username, password: user1Password, @@ -57,11 +66,15 @@ describe('conversation', () => { userPoolClientId, }); + console.log('checkpoint e'); + const { username: user2Username, password: user2Password } = await createCognitoUser({ region, userPoolId, }); + console.log('checkpoint f'); + const { accessToken: user2AccessToken } = await signInCognitoUser({ username: user2Username, password: user2Password, @@ -69,6 +82,8 @@ describe('conversation', () => { userPoolClientId, }); + console.log('checkpoint g'); + accessToken = user1AccessToken; accessToken2 = user2AccessToken; }); @@ -590,15 +605,31 @@ describe('conversation', () => { }); }); +/** + * Full build and CDK deployment of an app into the given `projRoot` path. + * + * 1. Initializes default CDK template from `../backends/configurable-stack` into `projRoot`. Includes: + * 1. `esbuild` + * 2. Creates a "test definition" containing `./graphql/schema-conversation.graphql` + * 3. Writes the definition to the app for ingestion + deployment. + * 4. CDK deploy. + * + * @param projRoot + * @returns + */ const deployCdk = async (projRoot: string): Promise<{ apiEndpoint: string; userPoolClientId: string; userPoolId: string }> => { const templatePath = path.resolve(path.join(__dirname, '..', 'backends', 'configurable-stack')); const name = await initCDKProject(projRoot, templatePath, { additionalDependencies: ['esbuild'], }); + console.log(`CDK project initialized at ${projRoot}`); + const conversationSchemaPath = path.resolve(path.join(__dirname, 'graphql', 'schema-conversation.graphql')); const conversationSchema = fs.readFileSync(conversationSchemaPath).toString(); + console.log(`Schema loaded from ${conversationSchemaPath}`); + const testDefinitions: Record = { conversation: { schema: [conversationSchema].join('\n'), @@ -609,7 +640,13 @@ const deployCdk = async (projRoot: string): Promise<{ apiEndpoint: string; userP writeStackConfig(projRoot, { prefix: 'Conversation' }); writeTestDefinitions(testDefinitions, projRoot); + const stashPath = `/var/tmp/e2e-stash-${path.basename(projRoot)}`; + await fs.copy(projRoot, stashPath); + console.log(`Project directory copied to ${stashPath}`); + const outputs = await cdkDeploy(projRoot, '--all'); + console.log(`CDK deployed to ${projRoot}`); + const { awsAppsyncApiEndpoint, UserPoolClientId, UserPoolId } = outputs[name]; return { apiEndpoint: awsAppsyncApiEndpoint, userPoolClientId: UserPoolClientId, userPoolId: UserPoolId }; }; diff --git a/packages/amplify-graphql-api-construct-tests/src/commands.ts b/packages/amplify-graphql-api-construct-tests/src/commands.ts index d7fdca9d22..53bd33d0de 100644 --- a/packages/amplify-graphql-api-construct-tests/src/commands.ts +++ b/packages/amplify-graphql-api-construct-tests/src/commands.ts @@ -133,6 +133,7 @@ export const cdkDeploy = async (cwd: string, option: string, props?: CdkDeployPr // npx cdk does not work on verdaccio env: { npm_config_registry: 'https://registry.npmjs.org/' }, noOutputTimeout, + stdio: 'inherit', }; await spawn( diff --git a/packages/amplify-graphql-api-construct-tests/src/utils/stack-config.ts b/packages/amplify-graphql-api-construct-tests/src/utils/stack-config.ts index eccccd08c1..0d9e45775c 100644 --- a/packages/amplify-graphql-api-construct-tests/src/utils/stack-config.ts +++ b/packages/amplify-graphql-api-construct-tests/src/utils/stack-config.ts @@ -21,6 +21,11 @@ export interface StackConfig { sqlLambdaLayerArn?: string; } +/** + * Stringifies `stackConfig` and writes it to `${projRoot}/stack-config.json` ... + * @param projRoot + * @param stackConfig + */ export const writeStackConfig = (projRoot: string, stackConfig: StackConfig): void => { const filePath = path.join(projRoot, 'stack-config.json'); fs.writeFileSync(filePath, JSON.stringify(stackConfig)); diff --git a/packages/amplify-graphql-api-construct/.jsii b/packages/amplify-graphql-api-construct/.jsii index cdc93f59b8..98d01516b5 100644 --- a/packages/amplify-graphql-api-construct/.jsii +++ b/packages/amplify-graphql-api-construct/.jsii @@ -6,7 +6,7 @@ ] }, "bundled": { - "@aws-amplify/ai-constructs": "^1.2.4", + "@aws-amplify/ai-constructs": "1.3.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.2.3", @@ -27,7 +27,7 @@ "@aws-amplify/graphql-transformer-core": "3.4.3", "@aws-amplify/graphql-transformer-interfaces": "4.2.6", "@aws-amplify/graphql-validate-transformer": "1.1.3", - "@aws-amplify/platform-core": "^1.0.0", + "@aws-amplify/platform-core": "1.6.5", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", @@ -1367,6 +1367,19 @@ } } }, + "aws-cdk-lib.aws_dsql": { + "targets": { + "dotnet": { + "package": "Amazon.CDK.AWS.DSQL" + }, + "java": { + "package": "software.amazon.awscdk.services.dsql" + }, + "python": { + "module": "aws_cdk.aws_dsql" + } + } + }, "aws-cdk-lib.aws_dynamodb": { "targets": { "dotnet": { @@ -9513,5 +9526,5 @@ } }, "version": "1.20.1", - "fingerprint": "+StR3FqEAlc1IFZPMqvjS5CUe5YnkTLZS2rMNf1BHPA=" + "fingerprint": "cjIOrGVQMbbVMZ2swsYNXbBb+JtmPtIgXzc4zjF4f58=" } \ No newline at end of file diff --git a/packages/amplify-graphql-api-construct/package.json b/packages/amplify-graphql-api-construct/package.json index f2c394b640..d54f5bae83 100644 --- a/packages/amplify-graphql-api-construct/package.json +++ b/packages/amplify-graphql-api-construct/package.json @@ -162,7 +162,7 @@ "zod" ], "dependencies": { - "@aws-amplify/ai-constructs": "^1.2.4", + "@aws-amplify/ai-constructs": "1.3.0", "@aws-amplify/backend-output-schemas": "^1.0.0", "@aws-amplify/backend-output-storage": "^1.0.0", "@aws-amplify/graphql-auth-transformer": "4.2.3", @@ -183,7 +183,7 @@ "@aws-amplify/graphql-transformer-core": "3.4.3", "@aws-amplify/graphql-transformer-interfaces": "4.2.6", "@aws-amplify/graphql-validate-transformer": "1.1.3", - "@aws-amplify/platform-core": "^1.0.0", + "@aws-amplify/platform-core": "1.6.5", "@aws-amplify/plugin-types": "^1.0.0", "@aws-crypto/crc32": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", diff --git a/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts b/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts index 6c9d0f9ead..48639ab9ad 100644 --- a/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts +++ b/packages/amplify-graphql-auth-transformer/src/__tests__/iam-custom-operations.test.ts @@ -514,6 +514,45 @@ describe('Custom operations have @aws_iam directives when enableIamAuthorization expect(out.schema).toMatch(/type EventInvocationResponse.*@aws_iam/); }); + test('Does not add @aws_iam to interfaces', () => { + const strategy = makeStrategy(strategyType); + const schema = /* GraphQL */ ` + interface FooInterface { + id: ID! + } + type Foo { + description: String + } + type EventInvocationResponse @aws_api_key { + success: Boolean! + } + type Query { + getFooCustom: Foo + } + type Mutation { + updateFooCustom: Foo + doSomethingAsync(body: String!): EventInvocationResponse + @function(name: "FnDoSomethingAsync", invocationType: Event) + @auth(rules: [{ allow: public, provider: apiKey }]) + } + type Subscription { + onUpdateFooCustom: Foo @aws_subscribe(mutations: ["updateFooCustom"]) + } + `; + + const out = testTransform({ + schema, + dataSourceStrategies: constructDataSourceStrategies(schema, strategy), + authConfig: makeAuthConfig(), + synthParameters: makeSynthParameters(), + transformers: makeTransformers(), + sqlDirectiveDataSourceStrategies: makeSqlDirectiveDataSourceStrategies(schema, strategy), + }); + + // Also expect the custom type referenced by the custom operation to be authorized + expect(out.schema).not.toMatch(/interface FooInterface.*@aws_iam/); + }); + test('Does not add duplicate @aws_iam directive to custom type if already present', () => { const strategy = makeStrategy(strategyType); const schema = /* GraphQL */ ` diff --git a/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts index 04c4dfa4f5..5562c4b582 100644 --- a/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts +++ b/packages/amplify-graphql-auth-transformer/src/graphql-auth-transformer.ts @@ -394,10 +394,15 @@ export class AuthTransformer extends TransformerAuthBase implements TransformerA return !type.directives?.some((dir) => dir.name.value === 'model'); }; + const isNotInterface = (type: TypeDefinitionNode): boolean => { + return type.kind !== Kind.INTERFACE_TYPE_DEFINITION; + }; + ctx.inputDocument.definitions .filter(isObjectTypeDefinitionNode) .filter(isNonModelType) .filter(needsAwsIamDirective) + .filter(isNotInterface) .forEach((def) => extendTypeWithDirectives(ctx, def.name.value, [makeDirective('aws_iam', [])])); }; diff --git a/yarn.lock b/yarn.lock index 640d48d3df..fdf2944613 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,7 +10,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@aws-amplify/ai-constructs@^1.2.4": +"@aws-amplify/ai-constructs@1.3.0", "@aws-amplify/ai-constructs@^1.2.4": version "1.3.0" resolved "https://registry.npmjs.org/@aws-amplify/ai-constructs/-/ai-constructs-1.3.0.tgz#c434746f80d36608c9b10bb01947903114c95bcb" integrity sha512-iek3TWI3iy8vlRLG9nHsultdeLZaEnElKnSW2cBwBdJo9jIcJQUs2iJx3bBGedsk2bbigVE2l8o2M50Ef2vm7Q== @@ -375,7 +375,7 @@ fflate "0.7.3" pako "2.0.4" -"@aws-amplify/platform-core@^1.0.0", "@aws-amplify/platform-core@^1.6.5": +"@aws-amplify/platform-core@1.6.5", "@aws-amplify/platform-core@^1.6.5": version "1.6.5" resolved "https://registry.npmjs.org/@aws-amplify/platform-core/-/platform-core-1.6.5.tgz#b36999fcc498bd65781f4238180c2c3e4b8dd4aa" integrity sha512-joWzx9htrYa3qdUVtbdo9mcHjkeKcMkwhYggLAHtCSkFhFc5yvI7QE26+CB+A0Rka4JuCAt+0MR8MdGNs+qftA== @@ -17022,16 +17022,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -17153,7 +17144,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -17174,13 +17165,6 @@ strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -18188,7 +18172,7 @@ workerpool@^6.5.1: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -18206,15 +18190,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"