-
Notifications
You must be signed in to change notification settings - Fork 365
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: [M3-7083 & M3-7175] MarketPlace regex fix + end to end coverage (#…
…9704) * fix: [fix-M3-7083] initial coomit: save work * Make utils and introduce new e2e * Asserting apps * Refactor hard coded new apps * Assert matching drawers * Assert drawer content * Save work * Assert filtering * Cleanup * Create linode from OCA * Cleanup * Moar Cleanup * Moar Cleanup * Fix unit test * Added changeset: Overly permissive regex in One Click Apps flow * Fix import
- Loading branch information
1 parent
d642c62
commit 77db948
Showing
19 changed files
with
616 additions
and
100 deletions.
There are no files selected for viewing
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 @@ | ||
--- | ||
"@linode/manager": Fixed | ||
--- | ||
|
||
Overly permissive regex in One Click Apps flow ([#9704](https://github.com/linode/manager/pull/9704)) |
249 changes: 249 additions & 0 deletions
249
packages/manager/cypress/e2e/core/oneClickApps/one-click-apps.spec.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,249 @@ | ||
import { containsClick, containsVisible } from 'support/helpers'; | ||
import { ui } from 'support/ui'; | ||
import { authenticate } from 'support/api/authentication'; | ||
import { cleanUp } from 'support/util/cleanup'; | ||
import { | ||
interceptGetStackScripts, | ||
mockGetStackScripts, | ||
} from 'support/intercepts/stackscripts'; | ||
import { interceptCreateLinode } from 'support/intercepts/linodes'; | ||
import { | ||
filterOneClickApps, | ||
handleAppLabel, | ||
} from 'src/features/Linodes/LinodesCreate/utilities'; | ||
import { randomLabel, randomString } from 'support/util/random'; | ||
import { chooseRegion } from 'support/util/regions'; | ||
import { | ||
mockAppendFeatureFlags, | ||
mockGetFeatureFlagClientstream, | ||
} from 'support/intercepts/feature-flags'; | ||
import { makeFeatureFlagData } from 'support/util/feature-flags'; | ||
import { mapStackScriptLabelToOCA } from 'src/features/OneClickApps/utils'; | ||
import { baseApps } from 'src/features/StackScripts/stackScriptUtils'; | ||
import { stackScriptFactory } from 'src/factories/stackscripts'; | ||
import { oneClickApps } from 'src/features/OneClickApps/oneClickApps'; | ||
|
||
import type { StackScript } from '@linode/api-v4'; | ||
import type { OCA } from '@src/features/OneClickApps/types'; | ||
|
||
authenticate(); | ||
|
||
describe('OneClick Apps (OCA)', () => { | ||
before(() => { | ||
cleanUp(['linodes']); | ||
}); | ||
|
||
it('Lists all the OneClick Apps', () => { | ||
interceptGetStackScripts().as('getStackScripts'); | ||
|
||
cy.visitWithLogin(`/linodes/create?type=One-Click`); | ||
|
||
cy.wait('@getStackScripts').then((xhr) => { | ||
const stackScripts: StackScript[] = xhr.response?.body.data ?? []; | ||
|
||
const trimmedApps: StackScript[] = filterOneClickApps({ | ||
baseApps, | ||
newApps: {}, | ||
queryResults: stackScripts, | ||
}); | ||
|
||
// Check the content of the OCA listing | ||
cy.findByTestId('one-click-apps-container').within(() => { | ||
// Check that all sections are present (note: New apps can be empty so not asserting its presence) | ||
cy.findByTestId('Popular apps').should('exist'); | ||
cy.findByTestId('All apps').should('exist'); | ||
|
||
trimmedApps.forEach((stackScript) => { | ||
const { decodedLabel, label } = handleAppLabel(stackScript); | ||
|
||
// Check that every OCA is listed with the correct label | ||
cy.get(`[data-qa-select-card-heading="${label}"]`).should('exist'); | ||
|
||
// Check that every OCA has a drawer match | ||
// This validates the regex in `mapStackScriptLabelToOCA` | ||
// and ensures every app listed has a corresponding populated drawer | ||
// This is only true for the apps defined in `oneClickApps.ts` | ||
expect( | ||
mapStackScriptLabelToOCA({ | ||
oneClickApps, | ||
stackScriptLabel: decodedLabel, | ||
}) | ||
).to.not.be.undefined; | ||
}); | ||
}); | ||
|
||
// Check drawer content for one OCA candidate | ||
const candidate = trimmedApps[0].label; | ||
const stackScriptCandidate = cy | ||
.get(`[data-qa-selection-card-info="${candidate}"]`) | ||
.first(); | ||
stackScriptCandidate.should('exist').click(); | ||
|
||
const app: OCA | undefined = mapStackScriptLabelToOCA({ | ||
oneClickApps, | ||
stackScriptLabel: candidate, | ||
}); | ||
|
||
ui.drawer | ||
.findByTitle(trimmedApps[0].label) | ||
.should('be.visible') | ||
.within(() => { | ||
containsVisible(app?.description); | ||
containsVisible(app?.summary); | ||
containsVisible(app?.website); | ||
}); | ||
ui.drawerCloseButton.find().click(); | ||
ui.drawer.find().should('not.exist'); | ||
|
||
// Check the filtering of the apps | ||
cy.scrollTo(0, 0); | ||
const initialNumberOfApps = trimmedApps.length; | ||
cy.findByPlaceholderText('Search for app name') | ||
.should('exist') | ||
.type(candidate); | ||
cy.findByTestId('one-click-apps-container').within(() => { | ||
cy.get('[data-qa-selection-card="true"]').should( | ||
'have.length.below', | ||
initialNumberOfApps | ||
); | ||
cy.get(`[data-qa-selection-card-info="${candidate}"]`).should( | ||
'be.visible' | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
it('Deploys a Linode from a One Click App', () => { | ||
const stackscriptId = 401709; | ||
const stackScripts = stackScriptFactory.build({ | ||
id: stackscriptId, | ||
username: 'linode', | ||
user_gravatar_id: '9d4d301385af69ceb7ad658aad09c142', | ||
label: 'E2E Test App', | ||
description: 'Minecraft OCA', | ||
ordinal: 10, | ||
logo_url: 'assets/Minecraft.svg', | ||
images: ['linode/debian11', 'linode/ubuntu20.04'], | ||
deployments_total: 18854, | ||
deployments_active: 412, | ||
is_public: true, | ||
mine: false, | ||
created: '2019-03-08T21:13:32', | ||
updated: '2023-09-26T15:00:45', | ||
rev_note: 'remove maxplayers hard coded options [oca-707]', | ||
script: '#!/usr/bin/env bash\n', | ||
user_defined_fields: [ | ||
{ | ||
name: 'username', | ||
label: | ||
"The username for the Linode's non-root admin/SSH user(must be lowercase)", | ||
example: 'lgsmuser', | ||
}, | ||
{ | ||
name: 'password', | ||
label: "The password for the Linode's non-root admin/SSH user", | ||
example: 'S3cuReP@s$w0rd', | ||
}, | ||
{ | ||
name: 'levelname', | ||
label: 'World Name', | ||
}, | ||
], | ||
}); | ||
|
||
const firstName = randomLabel(); | ||
const password = randomString(16); | ||
const image = 'linode/ubuntu20.04'; | ||
const rootPassword = randomString(16); | ||
const region = chooseRegion(); | ||
const linodeLabel = randomLabel(); | ||
const levelName = 'Get the enderman!'; | ||
|
||
mockGetStackScripts(stackScripts).as('getStackScripts'); | ||
mockAppendFeatureFlags({ | ||
oneClickApps: makeFeatureFlagData({ | ||
401709: 'E2E Test App', | ||
}), | ||
}).as('getFeatureFlags'); | ||
mockGetFeatureFlagClientstream().as('getClientStream'); | ||
|
||
cy.visitWithLogin(`/linodes/create?type=One-Click`); | ||
|
||
cy.wait('@getFeatureFlags'); | ||
cy.wait('@getStackScripts'); | ||
|
||
cy.findByTestId('one-click-apps-container').within(() => { | ||
// Since it is mock data we can assert the New App section is present | ||
cy.findByTestId('New apps').should('exist'); | ||
|
||
// Check that the app is listed and select it | ||
cy.get('[data-qa-selection-card="true"]').should('have.length', 3); | ||
cy.get(`[id=app-${stackscriptId}]`).first().should('be.visible').click(); | ||
}); | ||
|
||
// Input the user defined fields | ||
const userFieldId = | ||
"the-username-for-the-linode's-non-root-admin/ssh-user(must-be-lowercase)"; | ||
const passwordFieldId = | ||
"the-password-for-the-linode's-non-root-admin/ssh-user"; | ||
const levelNameFieldId = 'world-name'; | ||
|
||
cy.findByTestId('user-defined-fields-panel').within(() => { | ||
cy.get(`[id="${userFieldId}"]`) | ||
.should('be.visible') | ||
.click() | ||
.type(`${firstName}{enter}`); | ||
cy.get(`[id="${passwordFieldId}"]`) | ||
.should('be.visible') | ||
.click() | ||
.type(`${password}{enter}`); | ||
cy.get(`[id="${levelNameFieldId}"]`) | ||
.should('be.visible') | ||
.click() | ||
.type(`${levelName}{enter}`); | ||
|
||
// Check each field should persist when moving onto another field | ||
cy.get(`[id="${userFieldId}"]`).should('have.value', firstName); | ||
cy.get(`[id="${passwordFieldId}"]`).should('have.value', password); | ||
cy.get(`[id="${levelNameFieldId}"]`).should('have.value', levelName); | ||
}); | ||
|
||
// Choose an image | ||
cy.get('[data-qa-enhanced-select="Choose an image"]').within(() => { | ||
containsClick('Choose an image').type(`${image}{enter}`); | ||
}); | ||
|
||
// Choose a region | ||
cy.get(`[data-qa-enhanced-select="Select a Region"]`).within(() => { | ||
containsClick('Select a Region').type(`${region.id}{enter}`); | ||
}); | ||
|
||
// Choose a Linode plan | ||
cy.get('[data-qa-plan-row="Dedicated 8 GB"]') | ||
.closest('tr') | ||
.within(() => { | ||
cy.get('[data-qa-radio]').click(); | ||
}); | ||
|
||
// Enter a label. | ||
cy.findByText('Linode Label') | ||
.should('be.visible') | ||
.click() | ||
.type('{selectAll}{backspace}') | ||
.type(linodeLabel); | ||
|
||
// Choose a Root Password | ||
cy.get('[id="root-password"]').type(rootPassword); | ||
|
||
// Create the Linode | ||
interceptCreateLinode().as('createLinode'); | ||
ui.button | ||
.findByTitle('Create Linode') | ||
.should('be.visible') | ||
.should('be.enabled') | ||
.click(); | ||
|
||
cy.wait('@createLinode'); | ||
ui.toast.assertMessage(`Your Linode ${linodeLabel} is being created.`); | ||
}); | ||
}); |
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
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
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.