From f72d273000c35ae343481d09af5740e7c7e4f50a Mon Sep 17 00:00:00 2001 From: Andrew Wylde Date: Tue, 27 Feb 2024 10:39:09 -0600 Subject: [PATCH] refactor(applications): make deletion error static (#417) --- .../specs/application_registration.spec.ts | 154 ++++++++++++++---- src/locales/ca_ES.ts | 7 +- src/locales/de.ts | 7 +- src/locales/en.ts | 7 +- src/locales/es_ES.ts | 7 +- src/locales/fr.ts | 7 +- src/locales/i18n-type.d.ts | 5 + src/mixins/toaster.ts | 28 ---- src/views/MyApps.vue | 53 ++++-- 9 files changed, 197 insertions(+), 78 deletions(-) delete mode 100644 src/mixins/toaster.ts diff --git a/cypress/e2e/specs/application_registration.spec.ts b/cypress/e2e/specs/application_registration.spec.ts index c175a132..de71afed 100644 --- a/cypress/e2e/specs/application_registration.spec.ts +++ b/cypress/e2e/specs/application_registration.spec.ts @@ -481,46 +481,116 @@ describe('Application Registration', () => { cy.get(submitButton).click() cy.contains(apps[0].name + 'z') }) - it('can delete an existing application', () => { - cy.mockApplications(apps, 4) - cy.visit('/my-apps') - mockApplicationWithCredAndReg(apps[0]) + describe('Delete Application', () => { + it('can delete an existing application', () => { + cy.mockApplications(apps, 4) + cy.visit('/my-apps') - cy.get('[data-testid="applications-table"] tbody tr') - .should('have.length', 4) - .contains(apps[0].name) - .click() + mockApplicationWithCredAndReg(apps[0]) - cy.get('[data-testid="application-update-button"]').click() - cy.get('header h1').should('contain', 'Update Application') + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 4) + .contains(apps[0].name) + .click() - // Delete and cancel during confirmation - cy.get('[data-testid="application-delete-button"]').click() - cy.get('[data-testid="application-delete-modal"]').should('exist') - cy.get('[data-testid="application-delete-cancel-button"]').click() - cy.get('[data-testid="application-delete-modal"]').should('not.exist') + cy.get('[data-testid="application-update-button"]').click() + cy.get('header h1').should('contain', 'Update Application') - cy.intercept('DELETE', `api/v2/applications/${apps[0].id}`, { - statusCode: 200 - }).as('deleteApplication') + // Delete and cancel during confirmation + cy.get('[data-testid="application-delete-button"]').click() + cy.get('[data-testid="application-delete-modal"]').should('exist') + cy.get('[data-testid="application-delete-cancel-button"]').click() + cy.get('[data-testid="application-delete-modal"]').should('not.exist') - cy.mockApplications([...apps.slice(1)], 2) + cy.intercept('DELETE', `api/v2/applications/${apps[0].id}`, { + statusCode: 200 + }).as('deleteApplication') - // Delete and confirm deletion - cy.get('[data-testid="application-delete-button"]').click() - cy.get('[data-testid="application-delete-modal"]').should('exist') - cy.get('[data-testid="application-delete-confirm-button"]').click() + cy.mockApplications([...apps.slice(1)], 2) - cy.get('.toaster-container-outer .message').should( - 'contain', - 'Application successfully deleted' - ) + // Delete and confirm deletion + cy.get('[data-testid="application-delete-button"]').click() + cy.get('[data-testid="application-delete-modal"]').should('exist') + cy.get('[data-testid="application-delete-confirm-button"]').click() - cy.get('[data-testid="applications-table"] tbody tr') - .should('have.length', 3) - .contains(apps[0].name) - .should('not.exist') + cy.get('.toaster-container-outer .message').should( + 'contain', + 'Application successfully deleted' + ) + + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 3) + .contains(apps[0].name) + .should('not.exist') + }) + it('can delete an existing application from actions dropdown', () => { + cy.mockApplications(apps, 4) + cy.visit('/my-apps') + + mockApplicationWithCredAndReg(apps[0]) + + cy.intercept('DELETE', `api/v2/applications/${apps[0].id}`, { + statusCode: 200 + }).as('deleteApplication') + + // Delete and confirm deletion + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 4) + .contains(apps[0].name) + .get(`[data-testid="actions-dropdown-${apps[0].id}"]`) + .click() + + cy.mockApplications([...apps.slice(1)], 2) + + cy.get(`[data-testid="actions-dropdown-${apps[0].id}"] [data-testid="dropdown-delete-application"]`).click() + cy.get('[data-testid="application-delete-modal"]').should('exist') + cy.get('[data-testid="application-delete-confirm-button"]').click() + + cy.get('.toaster-container-outer .message').should( + 'contain', + 'Application successfully deleted' + ) + + cy.wait('@getApplications') + + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 3) + .contains(apps[0].name) + .should('not.exist') + }) + it('handles error when deleting an existing application', () => { + cy.mockApplications(apps, 4) + cy.visit('/my-apps') + + mockApplicationWithCredAndReg(apps[0]) + + cy.intercept('DELETE', `api/v2/applications/${apps[0].id}`, { + statusCode: 500, + body: { message: 'Error deleting application' } + }).as('deleteApplication') + + // Delete and confirm deletion + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 4) + .contains(apps[0].name) + .get(`[data-testid="actions-dropdown-${apps[0].id}"]`) + .click() + + cy.get(`[data-testid="actions-dropdown-${apps[0].id}"] [data-testid="dropdown-delete-application"]`).click() + cy.get('[data-testid="application-delete-modal"]').should('exist') + cy.get('[data-testid="application-delete-confirm-button"]').click() + + cy.get('[data-testid="delete-error-alert"]').should( + 'contain', + 'Error deleting application' + ) + + cy.get('[data-testid="applications-table"] tbody tr') + .should('have.length', 4) + .contains(apps[0].name) + .should('exist') + }) }) it('shows granted scopes if present ', () => { @@ -1483,6 +1553,28 @@ describe('Application Registration', () => { cy.get('[data-testid="application-secret-token-modal"]').should('not.exist') }) + it('handles failure to refresh token of existing application with dcr', () => { + cy.mockDcrPortal() + cy.mockApplications([{ ...apps[0] }], 1) + cy.visit('/my-apps') + + cy.get('[data-testid="applications-table"] tbody tr .actions-badge') + .should('have.length', 1) + .click() + + cy.intercept('POST', `api/v2/applications/${apps[0].id}/refresh-token`, { + statusCode: 500, + body: { error: 'Internal Server Error' } + }).as('refreshToken') + + cy.get('[data-testid="dropdown-delete-application"]').should('exist') + cy.get('[data-testid="dropdown-refresh-application-dcr-token"]').should('exist').click() + + cy.wait('@refreshToken') + + cy.get('[data-testid="refresh-error-alert"]').should('exist').should('contain', 'Failed to refresh secret') + }) + it('can refresh token of existing application with dcr from application page', () => { cy.mockApplications([{ ...apps[0], created_at: '2022-11-02T18:59:30.789Z' }], 1) mockApplicationWithCredAndReg({ ...apps[0], created_at: '2022-11-02T18:59:30.789Z' }) diff --git a/src/locales/ca_ES.ts b/src/locales/ca_ES.ts index e53d64da..854435ed 100644 --- a/src/locales/ca_ES.ts +++ b/src/locales/ca_ES.ts @@ -300,10 +300,13 @@ export const ca_ES: I18nType = { }, myApp: { authStrategyWarning: translationNeeded(en.application.authStrategyWarning), + authStrategyFetchError: (errString: string) => translationNeeded(en.myApp.authStrategyFetchError(errString)), newApp: 'Nova aplicació', plus: 'Més', myApps: 'Les meves aplicacions', refreshSecret: 'Actualitzar secret', + refreshSecretSuccess: translationNeeded(en.myApp.refreshSecretSuccess), + refreshSecretFailure: (error: string) => translationNeeded(en.myApp.refreshSecretFailure(error)), noSearchResults: translationNeeded(en.myApp.noSearchResults), searchPlaceholder: translationNeeded(en.myApp.searchPlaceholder), delete: 'Eliminar', @@ -311,7 +314,9 @@ export const ca_ES: I18nType = { noApp: 'Sense aplicacions', create: 'Crear una nova aplicació', getStarted: ' per començar', - deleteDialog: (name: string) => `Segur que voleu eliminar ${name}?Aquesta acció no es pot desfer.` + deleteDialog: (name: string) => `Segur que voleu eliminar ${name}?Aquesta acció no es pot desfer.`, + deleteSuccess: translationNeeded(en.myApp.deleteSuccess), + deleteFailure: (str: string) => translationNeeded(en.myApp.deleteFailure(str)) }, router: { portalTitle: 'Portal del desenvolupador', diff --git a/src/locales/de.ts b/src/locales/de.ts index 1e16d7bd..8ef94b28 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -300,10 +300,13 @@ export const de: I18nType = { }, myApp: { authStrategyWarning: translationNeeded(en.application.authStrategyWarning), + authStrategyFetchError: (errString: string) => translationNeeded(en.myApp.authStrategyFetchError(errString)), newApp: 'Neue Applikation', plus: 'Plus', myApps: 'Meine Applikationen', refreshSecret: 'Secret erneuern', + refreshSecretSuccess: translationNeeded(en.myApp.refreshSecretSuccess), + refreshSecretFailure: (error: string) => translationNeeded(en.myApp.refreshSecretFailure(error)), noSearchResults: translationNeeded(en.myApp.noSearchResults), searchPlaceholder: translationNeeded(en.myApp.searchPlaceholder), delete: 'Löschen', @@ -311,7 +314,9 @@ export const de: I18nType = { noApp: 'Keine Applikationen', create: 'Neue Applikation anlegen', getStarted: ' um loszulegen', - deleteDialog: (name: string) => `Sind Sie sicher, dass Sie ${name} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.` + deleteDialog: (name: string) => `Sind Sie sicher, dass Sie ${name} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.`, + deleteSuccess: translationNeeded(en.myApp.deleteSuccess), + deleteFailure: (str: string) => translationNeeded(en.myApp.deleteFailure(str)) }, router: { portalTitle: 'Entwicklerportal', diff --git a/src/locales/en.ts b/src/locales/en.ts index c20ea790..a256f981 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -296,10 +296,13 @@ export const en = { }, myApp: { authStrategyWarning: 'You cannot create an application as this developer portal has no available application auth strategies. Please contact a developer portal admin.', + authStrategyFetchError: (errString: string) => `Error fetching auth strategies: ${errString}`, newApp: 'New App', plus: 'Plus', myApps: 'My Apps', refreshSecret: 'Refresh secret', + refreshSecretSuccess: 'Successfully refreshed secret', + refreshSecretFailure: (str: string) => `Failed to refresh secret: ${str}`, delete: 'Delete', cancel: 'Cancel', noApp: 'No Applications', @@ -307,7 +310,9 @@ export const en = { noSearchResults: 'No Applications Found', create: 'Create a new app', getStarted: ' to get started', - deleteDialog: (name: string) => `Are you sure you want to delete ${name}? This action cannot be undone.` + deleteDialog: (name: string) => `Are you sure you want to delete ${name}? This action cannot be undone.`, + deleteSuccess: 'Application successfully deleted', + deleteFailure: (str: string) => `Failed to delete application: ${str}` }, router: { portalTitle: 'Developer Portal', diff --git a/src/locales/es_ES.ts b/src/locales/es_ES.ts index efe71130..45a6548f 100644 --- a/src/locales/es_ES.ts +++ b/src/locales/es_ES.ts @@ -300,10 +300,13 @@ export const es_ES: I18nType = { }, myApp: { authStrategyWarning: translationNeeded(en.application.authStrategyWarning), + authStrategyFetchError: (errString: string) => translationNeeded(en.myApp.authStrategyFetchError(errString)), newApp: 'Nueva aplicación', plus: 'Plus', myApps: 'Mis aplicaciones', refreshSecret: 'Refrescar clave secreta', + refreshSecretSuccess: translationNeeded(en.myApp.refreshSecretSuccess), + refreshSecretFailure: (error: string) => translationNeeded(en.myApp.refreshSecretFailure(error)), noSearchResults: translationNeeded(en.myApp.noSearchResults), searchPlaceholder: translationNeeded(en.myApp.searchPlaceholder), delete: 'Eliminar', @@ -311,7 +314,9 @@ export const es_ES: I18nType = { noApp: 'No hay aplicaciones', create: 'Crear una nueva aplicación', getStarted: ' para empezar', - deleteDialog: (name: string) => `¿Estas seguro que quieres borrar ${name}? Esta acción no se puede deshacer.` + deleteDialog: (name: string) => `¿Estas seguro que quieres borrar ${name}? Esta acción no se puede deshacer.`, + deleteSuccess: translationNeeded(en.myApp.deleteSuccess), + deleteFailure: (str: string) => translationNeeded(en.myApp.deleteFailure(str)) }, router: { portalTitle: 'Portal de desarrolladores', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 9d51cf3c..7654efdb 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -300,10 +300,13 @@ export const fr: I18nType = { }, myApp: { authStrategyWarning: translationNeeded(en.application.authStrategyWarning), + authStrategyFetchError: (errString: string) => translationNeeded(en.myApp.authStrategyFetchError(errString)), newApp: 'Nouvelle application', plus: 'Plus', myApps: 'Mes applications', refreshSecret: 'Actualiser le secret', + refreshSecretSuccess: translationNeeded(en.myApp.refreshSecretSuccess), + refreshSecretFailure: (error: string) => translationNeeded(en.myApp.refreshSecretFailure(error)), noSearchResults: translationNeeded(en.myApp.noSearchResults), searchPlaceholder: translationNeeded(en.myApp.searchPlaceholder), delete: 'Supprimer', @@ -311,7 +314,9 @@ export const fr: I18nType = { noApp: 'Aucune application', create: 'Créer une nouvelle application', getStarted: ' pour commencer', - deleteDialog: (name: string) => `Voulez-vous vraiment supprimer ${name} ? Cette action est irréversible.` + deleteDialog: (name: string) => `Voulez-vous vraiment supprimer ${name} ? Cette action est irréversible.`, + deleteSuccess: translationNeeded(en.myApp.deleteSuccess), + deleteFailure: (str: string) => translationNeeded(en.myApp.deleteFailure(str)) }, router: { portalTitle: 'Portail développeur', diff --git a/src/locales/i18n-type.d.ts b/src/locales/i18n-type.d.ts index 6b70f494..6cd1c996 100644 --- a/src/locales/i18n-type.d.ts +++ b/src/locales/i18n-type.d.ts @@ -296,10 +296,13 @@ export interface I18nType { }; myApp: { authStrategyWarning: string; + authStrategyFetchError: (errString: string) => string; newApp: string; plus: string; myApps: string; refreshSecret: string; + refreshSecretSuccess: string; + refreshSecretFailure: (error:string) => string; delete: string; cancel: string; searchPlaceholder: string; @@ -308,6 +311,8 @@ export interface I18nType { create: string; getStarted: string; deleteDialog: (name: string) => string; + deleteSuccess: string; + deleteFailure: (str: string) => string; }; router: { portalTitle: string; diff --git a/src/mixins/toaster.ts b/src/mixins/toaster.ts deleted file mode 100644 index f7875743..00000000 --- a/src/mixins/toaster.ts +++ /dev/null @@ -1,28 +0,0 @@ -const DEFAULT_TIMEOUT = 5000 - -export default { - data () { - return { - toaster: { - appearance: 'success', - message: '' - }, - successToaster: { - appearance: 'success', - message: 'success', - timeoutMilliseconds: DEFAULT_TIMEOUT - }, - errorToaster: { - appearance: 'danger', - message: 'error', - timeoutMilliseconds: DEFAULT_TIMEOUT - } - } - }, - - methods: { - showToaster (toasterConfig, tmanager) { - tmanager.open(toasterConfig) - } - } -} diff --git a/src/views/MyApps.vue b/src/views/MyApps.vue index 336fcabf..d5d69627 100644 --- a/src/views/MyApps.vue +++ b/src/views/MyApps.vue @@ -60,6 +60,18 @@
+ +