diff --git a/.eslint-dictionary.json b/.eslint-dictionary.json index c2cb3ac7d93..bde44b7bc2b 100644 --- a/.eslint-dictionary.json +++ b/.eslint-dictionary.json @@ -178,6 +178,7 @@ "uint", "unauth", "unlink", + "unstaged", "updateamplify", "urls", "userpool", diff --git a/packages/amplify-e2e-core/package.json b/packages/amplify-e2e-core/package.json index 5476a8824fd..5587cdfdf0e 100644 --- a/packages/amplify-e2e-core/package.json +++ b/packages/amplify-e2e-core/package.json @@ -30,6 +30,7 @@ "execa": "^5.1.1", "fs-extra": "^8.1.0", "graphql-transformer-core": "^7.6.6", + "ini": "^1.3.5", "jest-environment-node": "^26.6.2", "lodash": "^4.17.21", "node-fetch": "^2.6.7", diff --git a/packages/amplify-e2e-core/src/index.ts b/packages/amplify-e2e-core/src/index.ts index 2ee756a020e..80760e00968 100644 --- a/packages/amplify-e2e-core/src/index.ts +++ b/packages/amplify-e2e-core/src/index.ts @@ -1,3 +1,8 @@ +/* eslint-disable prefer-arrow/prefer-arrow-functions */ +/* eslint-disable func-style */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable import/no-cycle */ import * as os from 'os'; import * as path from 'path'; import * as fs from 'fs-extra'; @@ -12,12 +17,12 @@ export * from './configure'; export * from './init'; export * from './utils'; export * from './categories'; -export * from './utils/sdk-calls'; export * from './export'; export { addFeatureFlag } from './utils/feature-flags'; export * from './cli-version-controller'; declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace namespace NodeJS { interface Global { getRandomId: () => string; @@ -27,9 +32,6 @@ declare global { const amplifyTestsDir = 'amplify-e2e-tests'; -/** - * - */ export function getCLIPath(testingWithLatestCodebase = false) { if (!testingWithLatestCodebase) { if (process.env.AMPLIFY_PATH && fs.existsSync(process.env.AMPLIFY_PATH)) { @@ -43,16 +45,10 @@ export function getCLIPath(testingWithLatestCodebase = false) { return amplifyScriptPath; } -/** - * - */ export function isTestingWithLatestCodebase(scriptRunnerPath) { return scriptRunnerPath === process.execPath; } -/** - * - */ export function getScriptRunnerPath(testingWithLatestCodebase = false) { if (!testingWithLatestCodebase) { return process.platform === 'win32' ? 'node.exe' : 'exec'; @@ -62,9 +58,6 @@ export function getScriptRunnerPath(testingWithLatestCodebase = false) { return process.execPath; } -/** - * - */ export function getNpxPath() { let npxPath = 'npx'; if (process.platform === 'win32') { @@ -73,9 +66,6 @@ export function getNpxPath() { return npxPath; } -/** - * - */ export function getNpmPath() { let npmPath = 'npm'; if (process.platform === 'win32') { @@ -84,16 +74,6 @@ export function getNpmPath() { return npmPath; } -/** - * - */ -export function isCI(): boolean { - return !!(process.env.CI && process.env.CIRCLECI); -} - -/** - * - */ export function injectSessionToken(profileName: string) { const credentialsContents = ini.parse(fs.readFileSync(pathManager.getAWSCredentialsFilePath()).toString()); credentialsContents[profileName] = credentialsContents[profileName] || {}; @@ -101,16 +81,10 @@ export function injectSessionToken(profileName: string) { fs.writeFileSync(pathManager.getAWSCredentialsFilePath(), ini.stringify(credentialsContents)); } -/** - * - */ export function npmInstall(cwd: string) { spawnSync('npm', ['install'], { cwd }); } -/** - * - */ export async function installAmplifyCLI(version = 'latest') { spawnSync('npm', ['install', '-g', `@aws-amplify/cli@${version}`], { cwd: process.cwd(), @@ -122,11 +96,9 @@ export async function installAmplifyCLI(version = 'latest') { : path.join(os.homedir(), '.npm-global', 'bin', 'amplify'); } -/** - * - */ export async function createNewProjectDir( projectName: string, + // eslint-disable-next-line spellcheck/spell-checker prefix = path.join(fs.realpathSync(os.tmpdir()), amplifyTestsDir), ): Promise { const currentHash = execSync('git rev-parse --short HEAD', { cwd: __dirname }).toString().trim(); @@ -141,10 +113,8 @@ export async function createNewProjectDir( return projectDir; } -/** - * - */ export const createTempDir = () => { + // eslint-disable-next-line spellcheck/spell-checker const osTempDir = fs.realpathSync(os.tmpdir()); const tempProjectDir = path.join(osTempDir, amplifyTestsDir, uuid()); diff --git a/packages/amplify-e2e-core/src/init/headless-types.ts b/packages/amplify-e2e-core/src/init/headless-types.ts new file mode 100644 index 00000000000..55d0d9079c0 --- /dev/null +++ b/packages/amplify-e2e-core/src/init/headless-types.ts @@ -0,0 +1,13 @@ +/** + * Shape of awscloudformation object within `--profile` payload for init/pull + */ +export type AwsProviderConfig = { + configLevel: string, + useProfile: boolean, + profileName: string, +} + +/** + * Shape of `--categories` payload for init/pull + */ +export type CategoriesConfig = Record; diff --git a/packages/amplify-e2e-core/src/init/index.ts b/packages/amplify-e2e-core/src/init/index.ts index 27746525fed..e57f1a11d00 100644 --- a/packages/amplify-e2e-core/src/init/index.ts +++ b/packages/amplify-e2e-core/src/init/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ export * from './amplifyPull'; export * from './amplifyPush'; export * from './deleteProject'; @@ -5,3 +6,4 @@ export * from './initProjectHelper'; export * from './pull-headless'; export * from './adminUI'; export * from './overrideStack'; +export * from './non-interactive-init'; diff --git a/packages/amplify-e2e-core/src/init/non-interactive-init.ts b/packages/amplify-e2e-core/src/init/non-interactive-init.ts new file mode 100644 index 00000000000..4979a9b6f36 --- /dev/null +++ b/packages/amplify-e2e-core/src/init/non-interactive-init.ts @@ -0,0 +1,56 @@ +import execa from 'execa'; +// eslint-disable-next-line import/no-cycle +import { getCLIPath, TEST_PROFILE_NAME } from '..'; +import { CategoriesConfig, AwsProviderConfig } from './headless-types'; + +/** + * Executes a non-interactive init to attach a local project to an existing cloud environment + */ +export const nonInteractiveInitAttach = async ( + projRoot: string, + amplifyInitConfig: AmplifyInitConfig, + categoriesConfig?: CategoriesConfig, + awsProviderConfig = getAwsProviderConfig(), +): Promise => { + const args = [ + 'init', + '--yes', + '--amplify', + JSON.stringify(amplifyInitConfig), + '--providers', + JSON.stringify({ + awscloudformation: awsProviderConfig, + }), + ]; + if (categoriesConfig) { + args.push('--categories', JSON.stringify(categoriesConfig)); + } + await execa(getCLIPath(), args, { cwd: projRoot }); +}; + +/** + * Returns an AmplifyConfig object with a default editor + */ +export const getAmplifyInitConfig = (projectName: string, envName: string): AmplifyInitConfig => ({ + projectName, + envName, + defaultEditor: 'code', +}); + +/** + * Returns a default AwsProviderConfig + */ +export const getAwsProviderConfig = (): AwsProviderConfig => ({ + configLevel: 'project', + useProfile: true, + profileName: TEST_PROFILE_NAME, +}); + +/** + * Shape of `--amplify` payload for init/pull + */ +export type AmplifyInitConfig = { + projectName: string, + envName: string, + defaultEditor: string, +}; diff --git a/packages/amplify-e2e-core/src/init/overrideStack.ts b/packages/amplify-e2e-core/src/init/overrideStack.ts index 02a12323176..b94f3a52257 100644 --- a/packages/amplify-e2e-core/src/init/overrideStack.ts +++ b/packages/amplify-e2e-core/src/init/overrideStack.ts @@ -1,50 +1,36 @@ +/* eslint-disable jsdoc/require-jsdoc */ +// eslint-disable-next-line import/no-cycle import { nspawn as spawn, getCLIPath } from '..'; -export function amplifyOverrideRoot(cwd: string, settings: { testingWithLatestCodebase?: boolean }) { - return new Promise((resolve, reject) => { - const args = ['override', 'project']; +export const amplifyOverrideRoot = (cwd: string, settings: { testingWithLatestCodebase?: boolean }): Promise => { + const args = ['override', 'project']; - spawn(getCLIPath(settings.testingWithLatestCodebase), args, { cwd, stripColors: true }) - .wait('Do you want to edit override.ts file now?') - .sendNo() - .sendEof() - .run((err: Error) => { - if (!err) { - resolve({}); - } else { - reject(err); - } - }); - }); -} + return spawn(getCLIPath(settings.testingWithLatestCodebase), args, { cwd, stripColors: true }) + .wait('Do you want to edit override.ts file now?') + .sendNo() + .sendEof() + .runAsync(); +}; -export function amplifyOverrideAuth(cwd: string, settings: {}) { - return new Promise((resolve, reject) => { - const args = ['override', 'auth']; +export const amplifyOverrideAuth = (cwd: string): Promise => { + const args = ['override', 'auth']; - spawn(getCLIPath(), args, { cwd, stripColors: true }) - .wait('Do you want to edit override.ts file now?') - .sendNo() - .sendEof() - .run((err: Error) => { - if (!err) { - resolve({}); - } else { - reject(err); - } - }); - }); -} + return spawn(getCLIPath(), args, { cwd, stripColors: true }) + .wait('Do you want to edit override.ts file now?') + .sendNo() + .sendEof() + .runAsync(); +}; -export function amplifyOverrideApi(cwd: string, settings: any) { +export const amplifyOverrideApi = (cwd: string): Promise => { const args = ['override', 'api']; const chain = spawn(getCLIPath(), args, { cwd, stripColors: true }); chain.wait('Do you want to edit override.ts file now?').sendNo().sendEof(); return chain.runAsync(); -} +}; -export function buildOverrides(cwd: string, settings: any) { +export const buildOverrides = (cwd: string): Promise => { const args = ['build']; const chain = spawn(getCLIPath(), args, { cwd, stripColors: true }); return chain.runAsync(); -} +}; diff --git a/packages/amplify-e2e-core/src/init/pull-headless.ts b/packages/amplify-e2e-core/src/init/pull-headless.ts index 99ae91bdccf..9d33f0c5add 100644 --- a/packages/amplify-e2e-core/src/init/pull-headless.ts +++ b/packages/amplify-e2e-core/src/init/pull-headless.ts @@ -1,8 +1,14 @@ -import { nspawn as spawn, getCLIPath } from '..'; +import execa from 'execa'; import { EOL } from 'os'; +// eslint-disable-next-line import/no-cycle +import { + nspawn as spawn, getCLIPath, getAwsProviderConfig, +} from '..'; +import { CategoriesConfig } from './headless-types'; const defaultSettings = { name: EOL, + // eslint-disable-next-line spellcheck/spell-checker envName: 'integtest', editor: EOL, appType: EOL, @@ -16,37 +22,77 @@ const defaultSettings = { appId: '', }; -export function pullProject(cwd: string, settings: Object): Promise { +/** + * Executes amplify pull + */ +export const pullProject = (cwd: string, settings: Partial): Promise => { const s = { ...defaultSettings, ...settings }; - return new Promise((resolve, reject) => { - spawn(getCLIPath(), ['pull', '--appId', s.appId, '--envName', s.envName], { cwd, stripColors: true }) - .wait('Select the authentication method you want to use:') - .sendLine(s.useProfile) - .wait('Please choose the profile you want to use') - .sendLine(s.profileName) - .wait('Choose your default editor:') - .sendLine(s.editor) - .wait("Choose the type of app that you're building") - .sendLine(s.appType) - .wait('What javascript framework are you using') - .sendLine(s.framework) - .wait('Source Directory Path:') - .sendLine(s.srcDir) - .wait('Distribution Directory Path:') - .sendLine(s.distDir) - .wait('Build Command:') - .sendLine(s.buildCmd) - .wait('Start Command:') - .sendCarriageReturn() - .wait('Do you plan on modifying this backend?') - .sendConfirmNo() - .wait('Added backend environment config object to your project.') - .run((err: Error) => { - if (!err) { - resolve(); - } else { - reject(err); - } - }); - }); -} + return spawn(getCLIPath(), ['pull', '--appId', s.appId, '--envName', s.envName], { cwd, stripColors: true }) + .wait('Select the authentication method you want to use:') + .sendLine(s.useProfile) + .wait('Please choose the profile you want to use') + .sendLine(s.profileName) + .wait('Choose your default editor:') + .sendLine(s.editor) + .wait("Choose the type of app that you're building") + .sendLine(s.appType) + .wait('What javascript framework are you using') + .sendLine(s.framework) + .wait('Source Directory Path:') + .sendLine(s.srcDir) + .wait('Distribution Directory Path:') + .sendLine(s.distDir) + .wait('Build Command:') + .sendLine(s.buildCmd) + .wait('Start Command:') + .sendCarriageReturn() + .wait('Do you plan on modifying this backend?') + .sendConfirmNo() + .wait('Added backend environment config object to your project.') + .runAsync(); +}; + +/** + * Executes non-interactive pull command + */ +export const nonInteractivePullAttach = async ( + projRoot: string, + amplifyPullConfig: AmplifyPullConfig, + categoriesConfig?: CategoriesConfig, + awsProviderConfig = getAwsProviderConfig(), +): Promise => { + const args = [ + 'pull', + '--yes', + '--amplify', + JSON.stringify(amplifyPullConfig), + '--providers', + JSON.stringify({ + awscloudformation: awsProviderConfig, + }), + ]; + if (categoriesConfig) { + args.push('--categories', JSON.stringify(categoriesConfig)); + } + await execa(getCLIPath(), args, { cwd: projRoot }); +}; + +/** + * Shape of `--amplify` parameter for pull + */ +export type AmplifyPullConfig = { + projectName: string, + envName: string, + appId: string, + defaultEditor: string, +}; + +/** + * Returns a default AmplifyPullConfig + */ +export const getAmplifyPullConfig = (projectName: string, envName: string, appId: string): AmplifyPullConfig => ({ + projectName, + envName, + appId, + defaultEditor: 'code', +}); diff --git a/packages/amplify-e2e-core/src/utils/git-operations.ts b/packages/amplify-e2e-core/src/utils/git-operations.ts new file mode 100644 index 00000000000..6c709d5798b --- /dev/null +++ b/packages/amplify-e2e-core/src/utils/git-operations.ts @@ -0,0 +1,38 @@ +import execa from 'execa'; + +/** + * Initializes a git repo in the specified directory and configures a test name and email for commits + */ +export const gitInit = async (cwd: string): Promise => { + await execa('git', ['init'], { cwd }); + await execa('git', ['config', 'user.email', 'e2e-test@test.com'], { cwd }); + await execa('git', ['config', 'user.name', 'E2E Test'], { cwd }); +}; + +/** + * Stages all changed files and commits them + */ +export const gitCommitAll = async (cwd: string, message = 'e2e core committing all staged files'): Promise => { + await execa('git', ['add', '.'], { cwd }); + await execa('git', ['commit', '-m', message], { cwd }); +}; + +/** + * Executes `git clean -fdx` in the specified directory + */ +export const gitCleanFdx = async (cwd: string): Promise => { + await execa('git', ['clean', '-fdx'], { cwd }); +}; + +/** + * Returns a list of files that have unstaged changes in the specified directory + */ +export const gitChangedFiles = async (cwd: string): Promise => { + const { stdout } = await execa('git', ['diff', '--name-only'], { cwd }); + return stdout + .trim() + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0) + .sort(); +}; diff --git a/packages/amplify-e2e-core/src/utils/index.ts b/packages/amplify-e2e-core/src/utils/index.ts index 2edecda3be1..f4d43c722d7 100644 --- a/packages/amplify-e2e-core/src/utils/index.ts +++ b/packages/amplify-e2e-core/src/utils/index.ts @@ -25,7 +25,15 @@ export * from './sleep'; export * from './transformConfig'; export * from './admin-ui'; export * from './hooks'; -export * from './transform-current-project-to-git-pulled-project'; +export * from './git-operations'; + +/** + * Whether the current environment is CircleCI or not + */ +export const isCI = (): boolean => process.env.CI && process.env.CIRCLECI; + +// eslint-disable-next-line spellcheck/spell-checker +export const TEST_PROFILE_NAME = isCI() ? 'amplify-integ-test-user' : 'default'; // run dotenv config to update env variable config(); diff --git a/packages/amplify-e2e-core/src/utils/transform-current-project-to-git-pulled-project.ts b/packages/amplify-e2e-core/src/utils/transform-current-project-to-git-pulled-project.ts deleted file mode 100644 index f43fd24034f..00000000000 --- a/packages/amplify-e2e-core/src/utils/transform-current-project-to-git-pulled-project.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { pathManager } from 'amplify-cli-core'; -import * as fs from 'fs-extra'; -import glob from 'glob'; - -/** - * - * @param projRoot : string - * deleting files matching .gitignore regex - */ -export const transformCurrentProjectToGitPulledProject = (projRoot: string) => { - const gitIgnoreFilePath = pathManager.getGitIgnoreFilePath(projRoot); - const regexrArray = fs.readFileSync(gitIgnoreFilePath, 'utf-8').split('\n'); - regexrArray.forEach(str => { - if (str.endsWith('/')) { - str = str.split('/')[0]; - } - const dirPath = glob.sync(str, { - cwd: projRoot, - absolute: true, - matchBase: true, - }); - dirPath.forEach(file => { - try { - if (fs.existsSync(file)) { - if (fs.lstatSync(file).isDirectory()) { - fs.removeSync(file); - } else { - fs.unlinkSync(file); - } - } - } catch (err) {} - }); - }); -}; diff --git a/packages/amplify-e2e-tests/src/__tests__/api_7.test.ts b/packages/amplify-e2e-tests/src/__tests__/api_7.test.ts index 26a1959ba9b..a062cec5544 100644 --- a/packages/amplify-e2e-tests/src/__tests__/api_7.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/api_7.test.ts @@ -19,6 +19,7 @@ describe('amplify add api (GraphQL)', () => { let projRoot: string; let projFolderName: string; beforeEach(async () => { + // eslint-disable-next-line spellcheck/spell-checker projFolderName = 'graphqlapi'; projRoot = await createNewProjectDir(projFolderName); }); @@ -32,7 +33,9 @@ describe('amplify add api (GraphQL)', () => { }); it('init a project and add the simple_model api with transformer version 1', async () => { + // eslint-disable-next-line spellcheck/spell-checker const envName = 'devtest'; + // eslint-disable-next-line spellcheck/spell-checker const projName = 'simplemodel'; const cliInputsFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'cli-inputs.json'); await initJSProjectWithProfile(projRoot, { name: projName, envName }); @@ -44,6 +47,7 @@ describe('amplify add api (GraphQL)', () => { const meta = getProjectMeta(projRoot); const region = meta.providers.awscloudformation.Region; + // eslint-disable-next-line spellcheck/spell-checker const { output } = meta.api.simplemodel; const { GraphQLAPIIdOutput, GraphQLAPIEndpointOutput, GraphQLAPIKeyOutput } = output; const { graphqlApi } = await getAppSyncApi(GraphQLAPIIdOutput, region); @@ -65,15 +69,16 @@ describe('amplify add api (GraphQL)', () => { expect(error).toBeDefined(); expect(error.message).toContain(`${tableName} not found`); - await amplifyOverrideApi(projRoot, {}); + await amplifyOverrideApi(projRoot); const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-api-gql.ts'); const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'api', `${projName}`, 'override.ts'); fs.copyFileSync(srcOverrideFilePath, destOverrideFilePath); await amplifyPushOverride(projRoot); - // check overidden config - const overridenAppsyncApiOverrided = await getAppSyncApi(GraphQLAPIIdOutput, region); - expect(overridenAppsyncApiOverrided.graphqlApi).toBeDefined(); - expect(overridenAppsyncApiOverrided.graphqlApi.apiId).toEqual(GraphQLAPIIdOutput); - expect(overridenAppsyncApiOverrided.graphqlApi.xrayEnabled).toEqual(true); + // check overridden config + const overriddenAppsyncApiOverride = await getAppSyncApi(GraphQLAPIIdOutput, region); + expect(overriddenAppsyncApiOverride.graphqlApi).toBeDefined(); + expect(overriddenAppsyncApiOverride.graphqlApi.apiId).toEqual(GraphQLAPIIdOutput); + // eslint-disable-next-line spellcheck/spell-checker + expect(overriddenAppsyncApiOverride.graphqlApi.xrayEnabled).toEqual(true); }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/apigw.test.ts b/packages/amplify-e2e-tests/src/__tests__/apigw.test.ts index 3420719c56e..cfdb89876b0 100644 --- a/packages/amplify-e2e-tests/src/__tests__/apigw.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/apigw.test.ts @@ -102,12 +102,12 @@ describe('API Gateway e2e tests', () => { const restApiName = `e2eRestApi${shortId}`; await addRestApi(projRoot, { apiName: restApiName }); - await amplifyOverrideApi(projRoot, {}); + await amplifyOverrideApi(projRoot); const srcOverrideFilePath = path.join(__dirname, '..', '..', 'overrides', 'override-api-rest.ts'); const destOverrideTsFilePath = path.join(pathManager.getResourceDirectoryPath(projRoot, 'api', restApiName), 'override.ts'); fs.copyFileSync(srcOverrideFilePath, destOverrideTsFilePath); - await buildOverrides(projRoot, {}); + await buildOverrides(projRoot); const cfnPath = path.join( pathManager.getResourceDirectoryPath(projRoot, 'api', restApiName), diff --git a/packages/amplify-e2e-tests/src/__tests__/auth_6.test.ts b/packages/amplify-e2e-tests/src/__tests__/auth_6.test.ts index d05a7ed9f94..d89c7de18dd 100644 --- a/packages/amplify-e2e-tests/src/__tests__/auth_6.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/auth_6.test.ts @@ -84,7 +84,7 @@ describe('zero config auth', () => { expect(userPool.UserPool).toBeDefined(); // override new env - await amplifyOverrideAuth(projRoot, {}); + await amplifyOverrideAuth(projRoot); // this is where we will write our override logic to const destOverrideFilePath = path.join(projRoot, 'amplify', 'backend', 'auth', `${authResourceName}`, 'override.ts'); diff --git a/packages/amplify-e2e-tests/src/__tests__/frontend_config_drift.test.ts b/packages/amplify-e2e-tests/src/__tests__/frontend_config_drift.test.ts index 76fbed8f6c6..174161a7e53 100644 --- a/packages/amplify-e2e-tests/src/__tests__/frontend_config_drift.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/frontend_config_drift.test.ts @@ -1,11 +1,10 @@ import { - createNewProjectDir, deleteProject, deleteProjectDir, isCI, + createNewProjectDir, deleteProject, deleteProjectDir, TEST_PROFILE_NAME, } from '@aws-amplify/amplify-e2e-core'; import execa from 'execa'; import * as path from 'path'; const scriptPath = path.join(__dirname, '..', 'frontend-config-drift.sh'); -const awsProfile = isCI() ? 'amplify-integ-test-user' : 'default'; beforeAll(async () => { await execa.command(`chmod +x ${scriptPath}`); @@ -24,14 +23,14 @@ afterEach(async () => { describe('android config file', () => { it('has expected properties', async () => { - await execa(scriptPath, ['android', awsProfile], { cwd: projRoot, stdio: 'inherit' }); + await execa(scriptPath, ['android', TEST_PROFILE_NAME], { cwd: projRoot, stdio: 'inherit' }); // if script succeeds, test succeeds }); }); describe('ios config file', () => { it('has expected properties', async () => { - await execa(scriptPath, ['ios', awsProfile], { cwd: projRoot, stdio: 'inherit' }); + await execa(scriptPath, ['ios', TEST_PROFILE_NAME], { cwd: projRoot, stdio: 'inherit' }); // if script succeeds, test succeeds }); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/git-clone-attach.test.ts b/packages/amplify-e2e-tests/src/__tests__/git-clone-attach.test.ts new file mode 100644 index 00000000000..363c3a17856 --- /dev/null +++ b/packages/amplify-e2e-tests/src/__tests__/git-clone-attach.test.ts @@ -0,0 +1,127 @@ +/** + * Tests for headless init/pull workflows on git-cloned projects + * These tests exercise workflows that hosting executes during backend builds + */ + +import { + addAuthWithMaxOptions, + addFunction, + amplifyPushAuth, + buildOverrides, + createNewProjectDir, + deleteProject, + deleteProjectDir, + getAmplifyInitConfig, + getAmplifyPullConfig, + getProjectConfig, + getSocialProviders, + getTeamProviderInfo, + gitChangedFiles, + gitCleanFdx, + gitCommitAll, + gitInit, + initJSProjectWithProfile, + nonInteractiveInitAttach, + nonInteractivePullAttach, +} from '@aws-amplify/amplify-e2e-core'; +import { S3 } from 'aws-sdk'; +import { getShortId, importS3 } from '../import-helpers'; + +describe('attach amplify to git-cloned project', () => { + const envName = 'test'; + let projRoot: string; + const s3Client = new S3(); + const importBucketName = `git-clone-test-bucket-${getShortId()}`; + beforeAll(async () => { + await s3Client.createBucket({ Bucket: importBucketName }).promise(); + projRoot = await createNewProjectDir('clone-test'); + await initJSProjectWithProfile(projRoot, { envName, disableAmplifyAppCreation: false }); + await addFunction( + projRoot, + { + functionTemplate: 'Hello World', + environmentVariables: { + key: 'FOO_BAR', + value: 'fooBar', + }, + }, + 'nodejs', + ); + await addAuthWithMaxOptions(projRoot, {}); + await importS3(projRoot, importBucketName); + await amplifyPushAuth(projRoot); + await gitInit(projRoot); + await gitCommitAll(projRoot); + }); + + afterAll(async () => { + try { + await deleteProject(projRoot); + deleteProjectDir(projRoot); + } finally { + await s3Client.deleteBucket({ Bucket: importBucketName }).promise(); + } + }); + + test('headless init can be used to attach existing environment', async () => { + const { projectName } = getProjectConfig(projRoot); + const preCleanTpi = getTeamProviderInfo(projRoot); + const importBucketRegion = (Object.values(preCleanTpi[envName].categories.storage)[0] as any).region; + await gitCleanFdx(projRoot); + + // execute headless init + const categoriesConfig = { + storage: { + bucketName: importBucketName, + region: importBucketRegion, + }, + }; + await nonInteractiveInitAttach(projRoot, getAmplifyInitConfig(projectName, envName), categoriesConfig); + await buildOverrides(projRoot); + + // expect no file changes + const changedFiles = await gitChangedFiles(projRoot); + expect(changedFiles.length).toBe(0); + expect(getTeamProviderInfo(projRoot)).toEqual(preCleanTpi); + }); + + test('headless pull can be used to attach existing environment', async () => { + const { projectName } = getProjectConfig(projRoot); + const preCleanTpi = getTeamProviderInfo(projRoot); + const importBucketRegion = (Object.values(preCleanTpi[envName].categories.storage)[0] as any).region; + const appId = preCleanTpi[envName].awscloudformation.AmplifyAppId; + gitCleanFdx(projRoot); + + const socialProviders = getSocialProviders(); + const categoriesConfig = { + storage: { + bucketName: importBucketName, + region: importBucketRegion, + }, + auth: { + facebookAppIdUserPool: socialProviders.FACEBOOK_APP_ID, + facebookAppSecretUserPool: socialProviders.FACEBOOK_APP_SECRET, + googleAppIdUserPool: socialProviders.GOOGLE_APP_ID, + googleAppSecretUserPool: socialProviders.GOOGLE_APP_SECRET, + // eslint-disable-next-line spellcheck/spell-checker + loginwithamazonAppIdUserPool: socialProviders.AMAZON_APP_ID, + // eslint-disable-next-line spellcheck/spell-checker + loginwithamazonAppSecretUserPool: socialProviders.AMAZON_APP_SECRET, + }, + }; + + // execute headless pull + await nonInteractivePullAttach(projRoot, getAmplifyPullConfig(projectName, envName, appId), categoriesConfig); + await buildOverrides(projRoot); + + // expect no file changes + const changedFiles = await gitChangedFiles(projRoot); + expect(changedFiles).toMatchInlineSnapshot(` + Array [ + ".gitignore", + "amplify/README.md", + ] + `); // there is a .gitignore newline and the amplify/README.md file is modified after pull + expect(getTeamProviderInfo(projRoot)).toEqual(preCleanTpi); + }); +}); diff --git a/packages/amplify-e2e-tests/src/__tests__/hooks-a.test.ts b/packages/amplify-e2e-tests/src/__tests__/hooks-a.test.ts index 66825c92e4a..01bbc0a17d0 100644 --- a/packages/amplify-e2e-tests/src/__tests__/hooks-a.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/hooks-a.test.ts @@ -7,8 +7,10 @@ import { deleteProjectDir, getBackendAmplifyMeta, getHooksDirPath, + gitCleanFdx, + gitCommitAll, + gitInit, initJSProjectWithProfile, - transformCurrentProjectToGitPulledProject, } from '@aws-amplify/amplify-e2e-core'; import * as fs from 'fs-extra'; import * as path from 'path'; @@ -36,7 +38,9 @@ describe('runtime hooks', () => { await addFunction(projRoot, { functionTemplate: 'Hello World' }, 'nodejs'); await amplifyPushAuth(projRoot); // grab the appId from the meta file - transformCurrentProjectToGitPulledProject(projRoot); + await gitInit(projRoot); + await gitCommitAll(projRoot); + await gitCleanFdx(projRoot); await amplifyPullNonInteractive(projRoot, { appId, envName: 'staging' }); expect(fs.existsSync(hooksDirPath)).toBe(true); }); diff --git a/packages/amplify-e2e-tests/src/__tests__/init-special-case.test.ts b/packages/amplify-e2e-tests/src/__tests__/init-special-case.test.ts index 9eae6fce74c..ba435fbf350 100644 --- a/packages/amplify-e2e-tests/src/__tests__/init-special-case.test.ts +++ b/packages/amplify-e2e-tests/src/__tests__/init-special-case.test.ts @@ -8,8 +8,10 @@ import { getParameters, getProjectMeta, getTeamProviderInfo, + gitCleanFdx, + gitCommitAll, + gitInit, initJSProjectWithProfile, - transformCurrentProjectToGitPulledProject, updateAuthAddUserGroups, updatedInitNewEnvWithProfile, } from '@aws-amplify/amplify-e2e-core'; @@ -57,7 +59,9 @@ describe('amplify init', () => { const authResourceName = Object.keys(meta.auth)[0]; const category = 'auth'; - transformCurrentProjectToGitPulledProject(projectRoot); + await gitInit(projectRoot); + await gitCommitAll(projectRoot); + await gitCleanFdx(projectRoot); expect(() => { getParameters(projectRoot, category, authResourceName); }).toThrow();