diff --git a/frontend/src/pages/instance/Snapshots/index.vue b/frontend/src/pages/instance/Snapshots/index.vue index d0e79bb679..afcde23af3 100644 --- a/frontend/src/pages/instance/Snapshots/index.vue +++ b/frontend/src/pages/instance/Snapshots/index.vue @@ -225,6 +225,7 @@ export default { showRollbackDialog (snapshot) { Dialog.show({ header: 'Deploy Snapshot', + kind: 'danger', text: `This will overwrite the current instance. All changes to the flows, settings and environment variables made since the last snapshot will be lost. Are you sure you want to deploy to this snapshot?`, diff --git a/test/e2e/frontend/cypress/tests-ee/devices/snapshots.spec.js b/test/e2e/frontend/cypress/tests-ee/devices/snapshots.spec.js index 5cfba440a4..7bf2f86b20 100644 --- a/test/e2e/frontend/cypress/tests-ee/devices/snapshots.spec.js +++ b/test/e2e/frontend/cypress/tests-ee/devices/snapshots.spec.js @@ -289,4 +289,34 @@ describe('FlowForge - Devices - With Billing', () => { cy.get('[data-el="snapshots"] tbody').find('tr').contains('uploaded snapshot2') cy.get('[data-el="snapshots"] tbody').find('tr').contains('snapshot2 description') }) + it('Can rollback a snapshot', () => { + // Premise: Ensure the rollback endpoint is available and callable + // (NOTE: this is not testing the full mechanics of the rollback feature, only to prevent repeat regression. See #2032) + cy.intercept('PUT', '/api/*/devices/*').as('rollbackSnapshot') + + cy.intercept('GET', '/api/*/applications/*/snapshots*', deviceSnapshots).as('getSnapshots') + cy.intercept('GET', '/api/*/snapshots/*/full', deviceFullSnapshot).as('fullSnapshot') + + cy.contains('span', 'application-device-a').click() + cy.get('[data-nav="device-snapshots"]').click() + + // click kebab menu in row 1 + cy.get('[data-el="snapshots"] tbody').find('.ff-kebab-menu').eq(0).click() + + // click the Rollback Snapshot option + cy.get('[data-el="snapshots"] tbody .ff-kebab-menu .ff-kebab-options').find('.ff-list-item').eq(IDX_DEPLOY_SNAPSHOT).click() + + cy.get('[data-el="platform-dialog"]').should('be.visible') + cy.get('[data-el="platform-dialog"] .ff-dialog-header').contains('Deploy Snapshot to device') + + // find .ff-btn--danger with text "Confirm" and click it + cy.get('[data-el="platform-dialog"] .ff-btn--danger').contains('Confirm').click() + + // check body sent to /api/*/devices/* + cy.wait('@rollbackSnapshot').then(interception => { + const body = interception.request.body + expect(body).to.have.property('targetSnapshot') + expect(body.targetSnapshot).to.be.a('string') + }) + }) }) diff --git a/test/e2e/frontend/cypress/tests/instances/snapshots.spec.js b/test/e2e/frontend/cypress/tests/instances/snapshots.spec.js index be6aa7dad9..b12fae10fb 100644 --- a/test/e2e/frontend/cypress/tests/instances/snapshots.spec.js +++ b/test/e2e/frontend/cypress/tests/instances/snapshots.spec.js @@ -330,6 +330,30 @@ describe('FlowForge - Instance Snapshots', () => { cy.get('[data-el="snapshots"] tbody').find('tr').contains('uploaded snapshot2') cy.get('[data-el="snapshots"] tbody').find('tr').contains('snapshot2 description') }) + + it('Can rollback a snapshot', () => { + // Premise: Ensure the rollback endpoint is available and callable + // (NOTE: this is not testing the full mechanics of the rollback feature, only to prevent repeat regression. See #2032) + cy.intercept('POST', '/api/*/projects/*/actions/rollback').as('rollbackSnapshot') + + // click kebab menu in row 1 + cy.get('[data-el="snapshots"] tbody').find('.ff-kebab-menu').eq(0).click() + // click the Rollback Snapshot option + cy.get('[data-el="snapshots"] tbody .ff-kebab-menu .ff-kebab-options').find('.ff-list-item').eq(IDX_DEPLOY_SNAPSHOT).click() + + cy.get('[data-el="platform-dialog"]').should('be.visible') + cy.get('[data-el="platform-dialog"] .ff-dialog-header').contains('Deploy Snapshot') + + // find .ff-btn--danger with text "Confirm" and click it + cy.get('[data-el="platform-dialog"] .ff-btn--danger').contains('Confirm').click() + + // check body sent to /api/*/projects/*/actions/rollback + cy.wait('@rollbackSnapshot').then(interception => { + const body = interception.request.body + expect(body).to.have.property('snapshot') + expect(body.snapshot).to.be.a('string') + }) + }) }) describe('FlowForge shows audit logs', () => {