From 1e52cb0633ec73ff04dd02a0d125fbb655e0a0b6 Mon Sep 17 00:00:00 2001 From: cliu-akamai <126020611+cliu-akamai@users.noreply.github.com> Date: Tue, 16 May 2023 12:22:43 -0400 Subject: [PATCH] test: [M3-6550] - Add Domain delete, import a zone, and download zone file Cypress tests (#9111) * M3-6550: Add new Domain tests * Fix comments --- .../e2e/domains/smoke-delete-domain.spec.ts | 86 ++++++++++++++ .../smoke-domain-download-zone-file.spec.ts | 65 +++++++++++ .../smoke-domain-import-a-zone.spec.ts | 105 ++++++++++++++++++ packages/manager/cypress/support/helpers.ts | 8 ++ .../cypress/support/intercepts/domains.ts | 64 ++++++++++- packages/manager/src/factories/domain.ts | 10 +- 6 files changed, 336 insertions(+), 2 deletions(-) create mode 100644 packages/manager/cypress/e2e/domains/smoke-delete-domain.spec.ts create mode 100644 packages/manager/cypress/e2e/domains/smoke-domain-download-zone-file.spec.ts create mode 100644 packages/manager/cypress/e2e/domains/smoke-domain-import-a-zone.spec.ts diff --git a/packages/manager/cypress/e2e/domains/smoke-delete-domain.spec.ts b/packages/manager/cypress/e2e/domains/smoke-delete-domain.spec.ts new file mode 100644 index 00000000000..711667beb25 --- /dev/null +++ b/packages/manager/cypress/e2e/domains/smoke-delete-domain.spec.ts @@ -0,0 +1,86 @@ +import { Domain } from '@linode/api-v4/types'; +import { domainFactory } from '@src/factories'; +import { containsClick } from 'support/helpers'; +import { authenticate } from 'support/api/authentication'; +import { randomDomainName } from 'support/util/random'; +import { createDomain } from '@linode/api-v4/lib/domains'; +import { ui } from 'support/ui'; + +authenticate(); +describe('Delete a Domain', () => { + /* + * - Clicks "Delete" action menu item for domain but cancels operation. + * - Clicks "Delete" action menu item for domain and confirms operation. + * - Confirms that domain is still in landing page list after canceled operation. + * - Confirms that domain is removed from landing page list after confirmed operation. + */ + it('deletes a domain', () => { + const domainRequest = domainFactory.build({ + domain: randomDomainName(), + group: 'test-group', + }); + + cy.defer(createDomain(domainRequest)).then((domain: Domain) => { + cy.visitWithLogin('/domains'); + + // Confirm that domain is listed and initiate deletion. + cy.findByText(domain.domain) + .should('be.visible') + .closest('tr') + .within(() => { + ui.actionMenu + .findByTitle(`Action menu for Domain ${domain}`) + .should('be.visible') + .click(); + }); + ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); + + // Cancel deletion when prompted to confirm. + ui.dialog + .findByTitle(`Delete Domain ${domain.domain}?`) + .should('be.visible') + .within(() => { + ui.buttonGroup + .findButtonByTitle('Cancel') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // Confirm that domain is still listed and initiate deletion again. + cy.findByText(domain.domain) + .should('be.visible') + .closest('tr') + .within(() => { + ui.actionMenu + .findByTitle(`Action menu for Domain ${domain}`) + .should('be.visible') + .click(); + }); + ui.actionMenuItem.findByTitle('Delete').should('be.visible').click(); + + // Confirm deletion. + ui.dialog + .findByTitle(`Delete Domain ${domain.domain}?`) + .should('be.visible') + .within(() => { + // The button should be disabled before confirming the correct domain + ui.buttonGroup + .findButtonByTitle('Delete Domain') + .should('be.visible') + .should('be.disabled'); + + containsClick('Domain Name').type(domain.domain); + ui.buttonGroup + .findButtonByTitle('Delete Domain') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // Confirm that domain is deleted. + cy.visitWithLogin('/domains'); + cy.findByText(domain.domain).should('not.exist'); + }); + }); +}); diff --git a/packages/manager/cypress/e2e/domains/smoke-domain-download-zone-file.spec.ts b/packages/manager/cypress/e2e/domains/smoke-domain-download-zone-file.spec.ts new file mode 100644 index 00000000000..360a17d27c6 --- /dev/null +++ b/packages/manager/cypress/e2e/domains/smoke-domain-download-zone-file.spec.ts @@ -0,0 +1,65 @@ +import { + domainFactory, + domainRecordFactory, + domainZoneFileFactory, +} from '@src/factories'; +import { authenticate } from 'support/api/authentication'; +import { fbtClick, fbtVisible } from 'support/helpers'; +import { + mockGetDomains, + mockGetDomain, + mockGetDomainRecords, + mockGetDomainZoneFile, +} from 'support/intercepts/domains'; +import { randomDomainName } from 'support/util/random'; +import { readDownload } from 'support/util/downloads'; +import { ui } from 'support/ui'; + +authenticate(); +describe('Download a Zone file', () => { + /* + * - Clicks "Import A Zone" button and confirms operation. + * - Confirms that Domain won't be imported when the domain is empty or invalid. + * - Confirms that Domain won't be imported when the name server is empty or invalid. + * - Confirms that Domain exists after imported operation. + */ + it('downloads a zone in the domain page', () => { + const mockDomain = domainFactory.build({ + id: 123, + domain: randomDomainName(), + group: 'test-group', + }); + const mockDomainRecords = domainRecordFactory.build(); + const mockDomainZoneFile = domainZoneFileFactory.build(); + const mockZoneFileContents = mockDomainZoneFile.zone_file.join('\n'); + + cy.visitWithLogin('/domains'); + ui.button + .findByTitle('Import a Zone') + .should('be.visible') + .should('be.enabled') + .click(); + + mockGetDomains(mockDomain).as('getDomains'); + cy.visitWithLogin('/domains'); + cy.wait('@getDomains'); + + mockGetDomain(mockDomain.id, mockDomain).as('getDomain'); + mockGetDomainRecords(mockDomainRecords).as('getDomainRecords'); + fbtVisible(mockDomain.domain); + fbtClick(mockDomain.domain); + cy.wait('@getDomain'); + cy.wait('@getDomainRecords'); + + mockGetDomainZoneFile(mockDomain.id, mockDomainZoneFile).as( + 'getDomainZoneFile' + ); + ui.button + .findByTitle('Download DNS Zone File') + .should('be.visible') + .click(); + cy.wait('@getDomainZoneFile'); + + readDownload(`${mockDomain.domain}.txt`).should('eq', mockZoneFileContents); + }); +}); diff --git a/packages/manager/cypress/e2e/domains/smoke-domain-import-a-zone.spec.ts b/packages/manager/cypress/e2e/domains/smoke-domain-import-a-zone.spec.ts new file mode 100644 index 00000000000..a3179661c33 --- /dev/null +++ b/packages/manager/cypress/e2e/domains/smoke-domain-import-a-zone.spec.ts @@ -0,0 +1,105 @@ +import { ImportZonePayload } from '@linode/api-v4/types'; +import { domainFactory } from '@src/factories'; +import { authenticate } from 'support/api/authentication'; +import { fbltClick } from 'support/helpers'; +import { randomDomainName, randomIp } from 'support/util/random'; +import { mockGetDomains, mockImportDomain } from 'support/intercepts/domains'; +import { ui } from 'support/ui'; + +authenticate(); +describe('Import a Zone', () => { + /* + * - Clicks "Import A Zone" button and confirms operation. + * - Confirms that Domain won't be imported when the domain is empty or invalid. + * - Confirms that Domain won't be imported when the name server is empty or invalid. + * - Confirms that Domain exists after imported operation. + */ + it('imports a zone in the domain page', () => { + const zone: ImportZonePayload = { + domain: randomDomainName(), + remote_nameserver: randomIp(), + }; + + const mockDomain = domainFactory.build({ + domain: zone.domain, + group: 'test-group', + }); + + mockGetDomains(mockDomain).as('getDomains'); + cy.visitWithLogin('/domains'); + cy.wait('@getDomains'); + + ui.button + .findByTitle('Import a Zone') + .should('be.visible') + .should('be.enabled') + .click(); + + ui.drawer + .findByTitle('Import a Zone') + .should('be.visible') + .within(() => { + // The button should be disabled before providing any values + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.disabled'); + + // Verify only filling out Domain cannot import + fbltClick('Domain').clear().type(zone.domain); + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.enabled') + .click(); + cy.findByText('Remote nameserver is required.'); + + // Verify invalid domain cannot import + fbltClick('Domain').clear().type('1'); + fbltClick('Remote Nameserver').clear().type(zone.remote_nameserver); + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.enabled') + .click(); + cy.findByText('Domain is not valid.'); + + // Verify only filling out RemoteNameserver cannot import + fbltClick('Domain').clear(); + fbltClick('Remote Nameserver').clear().type(zone.remote_nameserver); + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.enabled') + .click(); + cy.findByText('Domain is required.'); + + // Verify invalid remote nameserver cannot import + fbltClick('Domain').clear().type(zone.domain); + fbltClick('Remote Nameserver').clear().type('1'); + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.enabled') + .click(); + cy.findByText(`The nameserver '1' is not valid.`); + + // Fill out and import the zone. + mockImportDomain(mockDomain).as('importDomain'); + mockGetDomains(mockDomain).as('getDomains'); + fbltClick('Domain').clear().type(zone.domain); + fbltClick('Remote Nameserver').clear().type(zone.remote_nameserver); + ui.buttonGroup + .findButtonByTitle('Import') + .should('be.visible') + .should('be.enabled') + .click(); + }); + + // Confirm that zone is imported. + cy.wait('@importDomain'); + cy.visitWithLogin('/domains'); + cy.wait('@getDomains'); + cy.findByText(zone.domain).should('be.visible'); + }); +}); diff --git a/packages/manager/cypress/support/helpers.ts b/packages/manager/cypress/support/helpers.ts index ce384806854..c052de1d658 100644 --- a/packages/manager/cypress/support/helpers.ts +++ b/packages/manager/cypress/support/helpers.ts @@ -25,3 +25,11 @@ export const fbtVisible = (text) => { export const fbtClick = (text) => { return cy.findByText(text).click(); }; + +export const fbltVisible = (text) => { + return cy.findByLabelText(text).should(visible); +}; + +export const fbltClick = (text) => { + return cy.findByLabelText(text).click(); +}; diff --git a/packages/manager/cypress/support/intercepts/domains.ts b/packages/manager/cypress/support/intercepts/domains.ts index c009928e4a1..feb3315cbfb 100644 --- a/packages/manager/cypress/support/intercepts/domains.ts +++ b/packages/manager/cypress/support/intercepts/domains.ts @@ -2,7 +2,7 @@ * @file Cypress intercepts and mocks for Domain API requests. */ -import type { Domain } from '@linode/api-v4/types'; +import type { Domain, DomainRecord, ZoneFile } from '@linode/api-v4/types'; import { apiMatcher } from 'support/util/intercepts'; import { paginateResponse } from 'support/util/paginate'; @@ -34,3 +34,65 @@ export const mockGetDomains = (domains: Domain[]): Cypress.Chainable => { export const interceptCreateDomainRecord = (): Cypress.Chainable => { return cy.intercept('POST', apiMatcher('domains/*/record*')); }; + +/** + * Intercepts GET request to get Domain records. + * + * @param records - an array of mock domain record objects + * + * @returns Cypress chainable. + */ +export const mockGetDomainRecords = ( + records: DomainRecord[] +): Cypress.Chainable => { + return cy.intercept( + 'GET', + apiMatcher('domains/*/record*'), + paginateResponse(records) + ); +}; + +/** + * Intercepts POST request to import a Domain Zone. + * + * @param domain - a mock domain object + * + * @returns Cypress chainable. + */ +export const mockImportDomain = (domain: Domain): Cypress.Chainable => { + return cy.intercept('POST', apiMatcher('domains/import'), domain); +}; + +/** + * Intercepts GET request to get a Domain detail. + * + * @param domainId - a mock domain ID + * @param domain - a mock domain + * + * @returns Cypress chainable. + */ +export const mockGetDomain = ( + domainId: string, + domain: Domain +): Cypress.Chainable => { + return cy.intercept('GET', apiMatcher(`domains/${domainId}`), domain); +}; + +/** + * Intercepts GET request to get a Domain detail. + * + * @param domainId - a mock domain ID + * @param zoneFile - a mock ZoneFile object + * + * @returns Cypress chainable. + */ +export const mockGetDomainZoneFile = ( + domainId: string, + zoneFile: ZoneFile +): Cypress.Chainable => { + return cy.intercept( + 'GET', + apiMatcher(`domains/${domainId}/zone-file`), + zoneFile + ); +}; diff --git a/packages/manager/src/factories/domain.ts b/packages/manager/src/factories/domain.ts index faf1f87238d..403f8f6207e 100644 --- a/packages/manager/src/factories/domain.ts +++ b/packages/manager/src/factories/domain.ts @@ -1,5 +1,9 @@ import * as Factory from 'factory.ts'; -import { Domain, DomainRecord } from '@linode/api-v4/lib/domains/types'; +import { + Domain, + DomainRecord, + ZoneFile, +} from '@linode/api-v4/lib/domains/types'; export const domainFactory = Factory.Sync.makeFactory({ domain: Factory.each((id) => `domain-${id}`), @@ -34,3 +38,7 @@ export const domainRecordFactory = Factory.Sync.makeFactory({ created: '2020-05-19T19:07:36', updated: '2020-05-19T19:07:36', }); + +export const domainZoneFileFactory = Factory.Sync.makeFactory({ + zone_file: ['test line 1', 'test line 2'], +});