test: 💍 Add delete resources e2e UI test (#1931)

✅ Closes: ICU-10897
This commit is contained in:
Stan Ryzhov authored and GitHub committed 2023-10-06 11:10:25 -04:00
1 parent 6915a1f5cb
commit 0357efee2a
7 files changed
+708 -9

No files matched your search

+10
View File
@@ -136,6 +136,16 @@ PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.co
PWDEBUG=console yarn playwright test --headed --config ./tests/e2e/playwright.config.js login.spec.js --debug
```
To run the tests with a specific tag (e.g. @enterprise), use this command.
```bash
PWDEBUG=console yarn playwright test --grep @enterprise --headed --config ./tests/e2e/playwright.config.js delete-resources.spec.js
```
To run all tests except those with a specific tag (e.g. @enterprise), use this command.
```bash
PWDEBUG=console yarn playwright test --grep-invert @enterprise --headed --config ./tests/e2e/playwright.config.js delete-resources.spec.js
```
[Playwright documentation about running tests](https://playwright.dev/docs/running-tests).
### Destroy Enos Scenario
+450 -1
View File
@@ -5,6 +5,7 @@
/* eslint-disable no-undef */
const { execSync, exec } = require('child_process');
const { nanoid } = require('nanoid');
/**
* Checks that the boundary cli is available
@@ -66,11 +67,33 @@ exports.connectToTarget = async (targetId) => {
return connect;
};
/**
* Uses the boundary CLI to create a new org.
* @returns {string} new org's id
*/
exports.createNewOrgCli = async () => {
const orgName = 'Org ' + nanoid();
let newOrg;
try {
newOrg = JSON.parse(
execSync(
`boundary scopes create \
-name="${orgName}" \
-scope-id=global \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return newOrg.id;
};
/**
* Uses the boundary CLI to delete the specified organization
* @param {string} orgId ID of the organization to be deleted
*/
exports.deleteOrg = async (orgId) => {
exports.deleteOrgCli = async (orgId) => {
try {
exec('boundary scopes delete -id=' + orgId);
} catch (e) {
@@ -78,6 +101,432 @@ exports.deleteOrg = async (orgId) => {
}
};
/**
* Uses the boundary CLI to create a new project.
* @param {string} orgId ID of the organization under which the project will be created.
* @returns {string} new project's ID
*/
exports.createNewProjectCli = async (orgId) => {
const projectName = 'Project ' + nanoid();
let newProject;
try {
newProject = JSON.parse(
execSync(
`boundary scopes create \
-name="${projectName}" \
-scope-id=${orgId} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return newProject.id;
};
/**
* Uses the boundary CLI to create a new controller-led worker.
* @returns {string} new worker's ID
*/
exports.createNewControllerLedWorkerCli = async () => {
const workerName = 'worker-' + nanoid();
let newWorker;
try {
newWorker = JSON.parse(
execSync(
`boundary workers create controller-led \
-name="${workerName.toLowerCase()}" \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return newWorker.id;
};
/**
* Uses the boundary CLI to create a new password auth-method.
* @param {string} scopeId ID of the scope under which the auth-method will be created.
* @returns {string} new auth-method's ID
*/
exports.createNewPasswordAuthMethodCli = async (scopeId) => {
const authMethodName = 'auth-method-' + nanoid();
let newAuthMethod;
try {
newAuthMethod = JSON.parse(
execSync(
`boundary auth-methods create password \
-name "${authMethodName}" \
-scope-id ${scopeId} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return newAuthMethod.id;
};
/**
* Uses the boundary CLI to make an auth-method primary.
* @param {string} scopeId ID of the scope that the auth-method belongs to.
* @param {string} authMethodId ID of the auth method that will be made primary.
*/
exports.makeAuthMethodPrimaryCli = async (scopeId, authMethodId) => {
try {
execSync(
`boundary scopes update \
-id=${scopeId} \
-primary-auth-method-id ${authMethodId} \
-format json`
);
} catch (e) {
console.log(`${e.stderr}`);
}
};
/**
* Uses the boundary CLI to create a new password account.
* @param {string} authMethodId ID of the auth-method that the new account will be created for.
* @returns {string} new account's ID
*/
exports.createNewPasswordAccountCli = async (authMethodId) => {
let passwordAccount;
const login = 'test-login';
process.env.ACCOUNT_PASSWORD = 'test-password';
try {
passwordAccount = JSON.parse(
execSync(
`boundary accounts create password \
-auth-method-id ${authMethodId} \
-login-name ${login} \
-password env://ACCOUNT_PASSWORD \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return passwordAccount.id;
};
/**
* Uses the boundary CLI to create a new role.
* @param {string} scopeId ID of the scope under which the role will be created.
* @returns {string} new role's ID
*/
exports.createNewRoleCli = async (scopeId) => {
const roleName = 'role-' + nanoid();
let role;
try {
role = JSON.parse(
execSync(
`boundary roles create \
-scope-id ${scopeId} \
-name ${roleName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return role.id;
};
/**
* Uses the boundary CLI to create a new group.
* @param {string} scopeId ID of the scope under which the group will be created.
* @returns {string} new group's ID
*/
exports.createNewGroupCli = async (scopeId) => {
const groupName = 'group-' + nanoid();
let group;
try {
group = JSON.parse(
execSync(
`boundary groups create \
-scope-id ${scopeId} \
-name ${groupName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return group.id;
};
/**
* Uses the boundary CLI to create a new user.
* @param {string} scopeId ID of the scope under which the user will be created.
* @returns {string} new user's ID
*/
exports.createNewUserCli = async (scopeId) => {
const userName = 'user-' + nanoid();
let user;
try {
user = JSON.parse(
execSync(
`boundary users create \
-scope-id ${scopeId} \
-name ${userName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return user.id;
};
/**
* Uses the boundary CLI to create a new static host catalog.
* @param {string} projectId ID of the project under which the host catalog will be created.
* @returns {string} new host catalog's ID
*/
exports.createNewStaticHostCatalogCli = async (projectId) => {
const hostCatalogName = 'static-host-catalog-' + nanoid();
let hostCatalog;
try {
hostCatalog = JSON.parse(
execSync(
`boundary host-catalogs create static \
-scope-id ${projectId} \
-name ${hostCatalogName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return hostCatalog.id;
};
/**
* Uses the boundary CLI to create a new dynamic AWS host catalog.
* @param {string} projectId ID of the project under which the host catalog will be created.
* @returns {string} new host catalog's ID
*/
exports.createDynamicAwsHostCatalogCli = async (projectId) => {
const hostCatalogName = 'dynamic-aws-host-catalog-' + nanoid();
let hostCatalog;
try {
hostCatalog = JSON.parse(
execSync(
`boundary host-catalogs create plugin \
-name ${hostCatalogName} \
-scope-id ${projectId} \
-plugin-name aws \
-attr disable_credential_rotation=true \
-attr region=us-east-1 \
-secret access_key_id=env://E2E_AWS_ACCESS_KEY_ID \
-secret secret_access_key=env://E2E_AWS_SECRET_ACCESS_KEY \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return hostCatalog.id;
};
/**
* Uses the boundary CLI to create a new static host.
* @param {string} hostCatalogId ID of the host catalog that the host will be created for.
* @returns {string} new host's ID
*/
exports.createNewStaticHostCli = async (hostCatalogId) => {
const hostName = 'static-host-' + nanoid();
let host;
try {
host = JSON.parse(
execSync(
`boundary hosts create static \
-host-catalog-id ${hostCatalogId} \
-name ${hostName} \
-address localhost \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return host.id;
};
/**
* Uses the boundary CLI to create a new static host set.
* @param {string} hostCatalogId ID of the host catalog that the host set will be created for.
* @returns {string} new host set's ID
*/
exports.createNewHostSetCli = async (hostCatalogId) => {
const hostSetName = 'static-host-' + nanoid();
let hostSet;
try {
hostSet = JSON.parse(
execSync(
`boundary host-sets create static \
-host-catalog-id ${hostCatalogId} \
-name ${hostSetName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return hostSet.id;
};
/**
* Uses the boundary CLI to create a new static credential store.
* @param {string} projectId ID of the project under which the credential store will be created.
* @returns {string} new credential store's ID
*/
exports.createNewStaticCredentialStoreCli = async (projectId) => {
const credentialStoreName = 'static-credential-store-' + nanoid();
let staticCredentialStore;
try {
staticCredentialStore = JSON.parse(
execSync(
`boundary credential-stores create static \
-scope-id ${projectId} \
-name ${credentialStoreName} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return staticCredentialStore.id;
};
/**
* Uses the boundary CLI to create a new vault credential store.
* @param {string} projectId ID of the project under which the credential store will be created.
* @param {string} secretPolicyName Name of the secret policy that's used to create vault token.
* @param {string} boundaryPolicyName Name of the boundary policy that's used to create vault token.
* @returns {string} new credential store's ID
*/
exports.createNewVaultCredentialStoreCli = async (
projectId,
secretPolicyName,
boundaryPolicyName
) => {
let credentialStore;
try {
execSync(
`vault policy write ${boundaryPolicyName} ./tests/e2e/tests/fixtures/boundary-controller-policy.hcl`
);
execSync(
`vault policy write ${secretPolicyName} ./tests/e2e/tests/fixtures/kv-policy.hcl`
);
const vaultToken = JSON.parse(
execSync(
`vault token create \
-no-default-policy=true \
-policy=${boundaryPolicyName} \
-policy=${secretPolicyName} \
-orphan=true \
-period=20m \
-renewable=true \
-format=json`
)
);
const clientToken = vaultToken.auth.client_token;
const credentialStoreName = 'vault-credential-store-' + nanoid();
credentialStore = JSON.parse(
execSync(
`boundary credential-stores create vault \
-scope-id ${projectId} \
-name ${credentialStoreName} \
-vault-address ${process.env.E2E_VAULT_ADDR} \
-vault-token ${clientToken} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return credentialStore.id;
};
/**
* Uses the boundary CLI to create a new username-password credential.
* @param {string} credentialStoreId ID of the credential store that the credential will be created for.
* @returns {string} new credential's ID
*/
exports.createNewUsernamePasswordCredentialCli = async (credentialStoreId) => {
let usernamePasswordCredential;
const login = 'test-login';
process.env.CREDENTIALS_PASSWORD = 'credentials-password';
try {
usernamePasswordCredential = JSON.parse(
execSync(
`boundary credentials create username-password \
-name ${login} \
-credential-store-id ${credentialStoreId} \
-username ${login} \
-password env://CREDENTIALS_PASSWORD \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return usernamePasswordCredential.id;
};
/**
* Uses the boundary CLI to create a new TCP target.
* @param {string} projectId ID of the project under which the target will be created.
* @returns {string} new target's ID
*/
exports.createNewTcpTarget = async (projectId) => {
const targetName = 'target-' + nanoid();
const defaultPort = 22;
let target;
try {
target = JSON.parse(
execSync(
`boundary targets create tcp \
-default-port ${defaultPort} \
-name ${targetName} \
-scope-id ${projectId} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return target.id;
};
/**
* Uses the boundary CLI to create a new SSH target.
* @param {string} projectId ID of the project under which the target will be created.
* @returns {string} new target's ID
*/
exports.createNewSshTarget = async (projectId) => {
const targetName = 'target-' + nanoid();
const defaultPort = 22;
let target;
try {
target = JSON.parse(
execSync(
`boundary targets create ssh \
-default-port ${defaultPort} \
-name ${targetName} \
-scope-id ${projectId} \
-format json`
)
).item;
} catch (e) {
console.log(`${e.stderr}`);
}
return target.id;
};
/**
* Uses the boundary CLI to get a session created for the specified target
* @param {string} orgName Name of the organization with the specified project
+30
View File
@@ -197,6 +197,36 @@ exports.createNewTargetWithAddress = async (page) => {
return targetName;
};
/**
* Uses the UI to delete a Boundary resource. Assume you have selected the desired resource.
* Note: For a resource to be deleted using this method,
* the resource page should allow to delete the resource using the Manage button.
* @param {Page} page Playwright page object
*/
exports.deleteResource = async (page) => {
await page.getByTitle('Manage').click();
await page.getByRole('button', { name: /^(Delete|Remove Worker)/ }).click();
await page.getByText('OK', { exact: true }).click();
await expect(
page.getByRole('alert').getByText('Success', { exact: true })
).toBeVisible();
await page.getByRole('button', { name: 'Dismiss' }).click();
};
/**
* Uses the UI to remove auth method as primary. Assume you have selected the desired auth method.
* @param {Page} page Playwright page object
*/
exports.removeAuthMethodAsPrimary = async (page) => {
await page.getByTitle('Manage').click();
await page.getByRole('button', { name: 'Remove as primary' }).click();
await page.getByText('OK', { exact: true }).click();
await expect(
page.getByRole('alert').getByText('Success', { exact: true })
).toBeVisible();
await page.getByRole('button', { name: 'Dismiss' }).click();
};
/**
* Uses the UI to add a host source to a target. Assume you have selected the desired target.
* @param {Page} page Playwright page object
+2 -2
View File
@@ -10,7 +10,7 @@ const { authenticatedState } = require('../helpers/general');
const {
authenticateBoundaryCli,
checkBoundaryCli,
deleteOrg,
deleteOrgCli,
} = require('../helpers/boundary-cli');
const {
createNewOrg,
@@ -50,6 +50,6 @@ test('Verify new auth-method can be created and assigned to users', async ({
await createNewUser(page, 'UI Test User');
await addAccountToUser(page);
} finally {
await deleteOrg(org.id);
await deleteOrgCli(org.id);
}
});
@@ -0,0 +1,210 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: MPL-2.0
*/
/* eslint-disable no-undef */
const { test } = require('@playwright/test');
const { authenticatedState, checkEnv } = require('../helpers/general');
const {
authenticateBoundaryCli,
checkBoundaryCli,
createNewOrgCli,
createNewProjectCli,
createNewControllerLedWorkerCli,
createNewPasswordAuthMethodCli,
makeAuthMethodPrimaryCli,
createNewPasswordAccountCli,
createNewRoleCli,
createNewGroupCli,
createNewUserCli,
createNewStaticHostCatalogCli,
createDynamicAwsHostCatalogCli,
createNewStaticHostCli,
createNewHostSetCli,
createNewStaticCredentialStoreCli,
createNewTcpTarget,
createNewSshTarget,
createNewVaultCredentialStoreCli,
createNewUsernamePasswordCredentialCli,
deleteOrgCli,
} = require('../helpers/boundary-cli');
const {
deleteResource,
removeAuthMethodAsPrimary,
} = require('../helpers/boundary-ui');
const { checkVaultCli } = require('../helpers/vault-cli');
const { execSync } = require('child_process');
const secretPolicyName = 'kv-policy';
const boundaryPolicyName = 'boundary-controller';
test.use({ storageState: authenticatedState });
test.beforeAll(async () => {
await checkEnv([
'VAULT_ADDR',
'VAULT_TOKEN',
'E2E_VAULT_ADDR',
'E2E_TARGET_ADDRESS',
'E2E_SSH_USER',
'E2E_SSH_KEY_PATH',
'E2E_AWS_ACCESS_KEY_ID',
'E2E_AWS_SECRET_ACCESS_KEY',
]);
await checkBoundaryCli();
await checkVaultCli();
});
test.beforeEach(async () => {
execSync(`vault policy delete ${secretPolicyName}`);
execSync(`vault policy delete ${boundaryPolicyName}`);
await authenticateBoundaryCli();
});
test('Verify resources can be deleted', async ({ page }) => {
let orgId;
try {
// Create boundary resources using CLI
orgId = await createNewOrgCli();
let projectId = await createNewProjectCli(orgId);
let workerId = await createNewControllerLedWorkerCli();
let authMethodId = await createNewPasswordAuthMethodCli(orgId);
await makeAuthMethodPrimaryCli(orgId, authMethodId);
let passwordAccountId = await createNewPasswordAccountCli(authMethodId);
let projectScopeRoleId = await createNewRoleCli(projectId);
let orgScopeRoleId = await createNewRoleCli(orgId);
let globalScopeRoleId = await createNewRoleCli('global');
let groupId = await createNewGroupCli(orgId);
let userId = await createNewUserCli(orgId);
let staticHostCatalogId = await createNewStaticHostCatalogCli(projectId);
let dynamicAwsHostCatalogId = await createDynamicAwsHostCatalogCli(
projectId
);
let staticHostId = await createNewStaticHostCli(staticHostCatalogId);
let staticHostSetId = await createNewHostSetCli(staticHostCatalogId);
let staticCredentialStoreId = await createNewStaticCredentialStoreCli(
projectId
);
let vaultCredentialStoreId = await createNewVaultCredentialStoreCli(
projectId,
secretPolicyName,
boundaryPolicyName
);
let usernamePasswordCredentialId =
await createNewUsernamePasswordCredentialCli(staticCredentialStoreId);
let tcpTargetId = await createNewTcpTarget(projectId);
// Delete TCP target
await page.goto(`/scopes/${projectId}/targets/${tcpTargetId}`);
await deleteResource(page);
// Delete username-password credentials
await page.goto(
`/scopes/${projectId}/credential-stores/${staticCredentialStoreId}/credentials/${usernamePasswordCredentialId}`
);
await deleteResource(page);
// Delete static credential store
await page.goto(
`/scopes/${projectId}/credential-stores/${staticCredentialStoreId}`
);
await deleteResource(page);
// Delete vault credential store
await page.goto(
`/scopes/${projectId}/credential-stores/${vaultCredentialStoreId}`
);
await deleteResource(page);
// Delete static host set
await page.goto(
`/scopes/${projectId}/host-catalogs/${staticHostCatalogId}/host-sets/${staticHostSetId}`
);
await deleteResource(page);
// Delete static host
await page.goto(
`/scopes/${projectId}/host-catalogs/${staticHostCatalogId}/hosts/${staticHostId}`
);
await deleteResource(page);
// Delete static host catalog
await page.goto(
`/scopes/${projectId}/host-catalogs/${staticHostCatalogId}`
);
await deleteResource(page);
// Delete dynamic aws host catalog
await page.goto(
`/scopes/${projectId}/host-catalogs/${dynamicAwsHostCatalogId}`
);
await deleteResource(page);
// Delete user
await page.goto(`/scopes/${projectId}/users/${userId}`);
await deleteResource(page);
// Delete group
await page.goto(`/scopes/${projectId}/groups/${groupId}`);
await deleteResource(page);
// Delete role under global scope
await page.goto(`/scopes/global/roles/${globalScopeRoleId}`);
await deleteResource(page);
// Delete role under org scope
await page.goto(`/scopes/${projectId}/roles/${orgScopeRoleId}`);
await deleteResource(page);
// Delete role under project scope
await page.goto(`/scopes/${projectId}/roles/${projectScopeRoleId}`);
await deleteResource(page);
// Delete password account
await page.goto(
`/scopes/${orgId}/auth-methods/${authMethodId}/accounts/${passwordAccountId}`
);
await deleteResource(page);
// Delete auth method
await page.goto(`/scopes/${orgId}/auth-methods/${authMethodId}`);
await removeAuthMethodAsPrimary(page);
await deleteResource(page);
// Delete worker
await page.goto(`/scopes/global/workers/${workerId}`);
await deleteResource(page);
// Delete project
await page.goto(`/scopes/${projectId}`);
await deleteResource(page);
// Delete org
await page.goto(`/scopes/${orgId}/edit`);
await deleteResource(page);
} finally {
// Delete org in case the test failed before deleting the org using UI
await deleteOrgCli(orgId);
}
});
test('Verify enterprise resources can be deleted @enterprise', async ({ page }) => {
let orgId;
try {
orgId = await createNewOrgCli();
let projectId = await createNewProjectCli(orgId);
// Create enterprise boundary resources using CLI
let sshTargetId = await createNewSshTarget(projectId);
// Delete SSH target
await page.goto(`/scopes/${projectId}/targets/${sshTargetId}`);
await deleteResource(page);
} finally {
await deleteOrgCli(orgId);
}
});
+2 -2
View File
@@ -10,7 +10,7 @@ const { authenticatedState } = require('../helpers/general');
const {
authenticateBoundaryCli,
checkBoundaryCli,
deleteOrg,
deleteOrgCli,
} = require('../helpers/boundary-cli');
const {
createNewOrg,
@@ -44,6 +44,6 @@ test('Verify a new role can be created and associated with a group', async ({
await addPrincipalToRole(page, groupName);
await addGrantsToGroup(page, 'ids=*;type=*;actions=read,list');
} finally {
await deleteOrg(org.id);
await deleteOrgCli(org.id);
}
});
+4 -4
View File
@@ -11,7 +11,7 @@ const {
authenticateBoundaryCli,
checkBoundaryCli,
connectToTarget,
deleteOrg,
deleteOrgCli,
} = require('../helpers/boundary-cli');
const {
createNewOrg,
@@ -73,7 +73,7 @@ test('Verify session created to target with host, then cancel the session', asyn
.getByRole('button', { name: 'Cancel' })
.click();
} finally {
await deleteOrg(org.id);
await deleteOrgCli(org.id);
// End `boundary connect` process
if (connect) {
connect.kill('SIGTERM');
@@ -112,7 +112,7 @@ test('Verify session created to target with address, then cancel the session', a
.getByRole('button', { name: 'Cancel' })
.click();
} finally {
await deleteOrg(org.id);
await deleteOrgCli(org.id);
// End `boundary connect` process
if (connect) {
connect.kill('SIGTERM');
@@ -150,6 +150,6 @@ test('Verify TCP target is updated', async ({ page }) => {
await authenticateBoundaryCli();
const orgs = JSON.parse(execSync('boundary scopes list -format json'));
const org = orgs.items.filter((obj) => obj.name === orgName)[0];
await deleteOrg(org.id);
await deleteOrgCli(org.id);
}
});