diff --git a/.vib/spring-cloud-dataflow/cypress/cypress.json b/.vib/spring-cloud-dataflow/cypress/cypress.json new file mode 100644 index 0000000000..d900eab5a3 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress.json @@ -0,0 +1,5 @@ +{ + "baseUrl": "http://localhost", + "defaultCommandTimeout": 30000, + "pageLoadTimeout": 240000 +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/applications.json b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/applications.json new file mode 100644 index 0000000000..061cfe650a --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/applications.json @@ -0,0 +1,8 @@ +{ + "newApplication": { + "streamApplicationType": "Stream application starters for Kafka/Maven", + "taskApplicationType": "Task application starters for Maven", + "streamApplication1": "mongodb", + "streamApplication2": "cassandra" + } +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/schedules.json b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/schedules.json new file mode 100644 index 0000000000..bee6cc078f --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/schedules.json @@ -0,0 +1,7 @@ +{ + "newSchedule": { + "name": "test-schedule", + "cronExpression": "*/5 * * * *", + "taskType": "timestamp" + } +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/streams.json b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/streams.json new file mode 100644 index 0000000000..5165ee0b45 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/streams.json @@ -0,0 +1,6 @@ +{ + "newStream": { + "name": "test-stream", + "type": "mongodb | cassandra" + } +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/task-to-import.json b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/task-to-import.json new file mode 100644 index 0000000000..e351f44143 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/task-to-import.json @@ -0,0 +1,13 @@ +{ + "date": 1650360393208, + "tasks": [ + { + "name": "test-task-imported", + "dslText": "timestamp", + "composed": false, + "status": "UNKNOWN", + "composedTaskElement": false, + "description": "" + } + ] +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/tasks.json b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/tasks.json new file mode 100644 index 0000000000..5b2633be25 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/fixtures/tasks.json @@ -0,0 +1,5 @@ +{ + "newTask": { + "name": "test-task" + } +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/integration/prepare_app_state.js b/.vib/spring-cloud-dataflow/cypress/cypress/integration/prepare_app_state.js new file mode 100644 index 0000000000..1b8f70d642 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/integration/prepare_app_state.js @@ -0,0 +1,26 @@ +/// +import { random } from './utils'; + +export const importAnApplication = (application) => { + cy.visit('/dashboard'); + cy.contains('button', 'Add application(s)').click(); + cy.contains('label', application).click(); + cy.contains('.btn-primary', 'Import').click(); + cy.get('.toast-container').should('contain', 'Application(s) Imported'); +}; + +export const createATask = (taskType) => { + cy.visit('/dashboard/#/tasks-jobs/tasks'); + cy.contains('button', 'Create task').click(); + cy.get('.CodeMirror-line').type(taskType); + cy.contains('#v-2', taskType); + cy.contains('button', 'Create task').click(); + cy.contains('.modal-content', 'Create task'); + cy.fixture('tasks').then((task) => { + cy.get('input[placeholder="Task Name"]').type( + `${task.newTask.name}-${random}` + ); + cy.get('input#desc').type('This is a task'); + cy.contains('button', 'Create the task').click(); + }); +}; diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/integration/spring_cloud_dataflow_spec.js b/.vib/spring-cloud-dataflow/cypress/cypress/integration/spring_cloud_dataflow_spec.js new file mode 100644 index 0000000000..8a5b971d0e --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/integration/spring_cloud_dataflow_spec.js @@ -0,0 +1,121 @@ +/// +import { random } from './utils.js'; +import { importAnApplication, createATask } from './prepare_app_state.js'; + +it('allows getting Spring Cloud Dataflow info', () => { + cy.visit('/dashboard'); + cy.get('.signpost-trigger').click(); + cy.get('.signpost-content-body') + .invoke('text') + .should('match', /.*[0-9].*/); + cy.contains('button', 'More info').click(); + cy.contains('.modal-content', 'Core') + .and('contain', 'Dashboard') + .and('contain', 'Shell'); +}); + +it('allows a stream to be created and deployed', () => { + cy.fixture('applications').then((application) => { + importAnApplication(application.newApplication.streamApplicationType); + }); + cy.visit('dashboard/#/streams/list'); + cy.contains('button', 'Create stream(s)').click(); + cy.fixture('streams').then((stream) => { + cy.get('.CodeMirror-line').type(stream.newStream.type); + }); + + cy.fixture('applications').then((application) => { + cy.contains('#v-2', application.newApplication.streamApplication1).and( + 'contain', + application.newApplication.streamApplication2 + ); + }); + + cy.contains('button', 'Create stream(s)').click(); + cy.get('.modal-content').should('contain', 'Create Stream'); + cy.fixture('streams').then((stream) => { + cy.get('input[placeholder="Stream Name"]').type( + `${stream.newStream.name}-${random}` + ); + cy.contains('button', 'Create the stream').click(); + cy.contains( + '.datagrid-inner-wrapper', + `${stream.newStream.name}-${random}` + ); + cy.contains('.toast-container', 'successfully'); + cy.contains('clr-dg-cell', 'UNDEPLOYED') + .siblings('clr-dg-cell', stream.newStream.name) + .first() + .click(); + }); + + cy.contains('button#btn-deploy', 'Deploy stream').click(); + cy.contains('button', 'Deploy stream').click(); + cy.get('.toast-container').should('contain', 'Deploy success'); +}); + +it('allows a task to be scheduled and destroyed', () => { + cy.fixture('applications').then((application) => { + importAnApplication(application.newApplication.taskApplicationType); + }); + cy.fixture('schedules').then((schedule) => { + createATask(schedule.newSchedule.taskType); + }); + cy.visit('dashboard/#/tasks-jobs/tasks'); + cy.fixture('tasks').then((task) => { + cy.contains('clr-dg-cell', 'UNKNOWN') + .siblings('clr-dg-cell', task.newTask.name) + .first() + .click(); + }); + cy.contains('button', 'Schedule').click(); + cy.fixture('schedules').then((schedule) => { + cy.get('input[name="example"]') + .first() + .type(`${schedule.newSchedule.name}-${random}`); + + cy.get('input[name="example"]') + .last() + .type(`${schedule.newSchedule.cronExpression}`); + }); + + cy.contains('button', 'Create schedule(s)').click(); + cy.contains('.toast-container', 'Successfully'); + cy.contains('a', 'Tasks').click(); + cy.contains('clr-dg-cell', 'UNKNOWN') + .siblings('clr-dg-cell', 'test-task-') + .first() + .click(); + cy.contains('button', 'Destroy task').click(); + cy.contains('button', 'Destroy the task').click(); + cy.contains('1 task definition(s) destroyed.'); +}); + +it('allows importing a task from a file and destroying it ', () => { + cy.visit('/dashboard/#/manage/tools'); + cy.contains('a', 'Import tasks from a JSON file').click(); + cy.get('input[type="file"]').selectFile( + 'cypress/fixtures/task-to-import.json', + { force: true } + ); + cy.contains('button', 'Import').click(); + cy.contains('1 task(s) created'); + cy.get('.close').click(); + cy.visit('dashboard/#/tasks-jobs/tasks'); + cy.contains('button', 'Group Actions').click(); + cy.get('[aria-label="Select All"]').click({ force: true }); + cy.contains('button', 'Destroy task').click(); + cy.contains('.modal-content', 'Confirm Destroy Task'); + cy.contains('button', 'Destroy the task').click(); + cy.get('.toast-container').should('contain', 'destroyed'); +}); + +it('allows unregistering of an application', () => { + cy.fixture('applications').then((application) => { + importAnApplication(application.newApplication.streamApplicationType); + }); + cy.get('clr-dg-cell').siblings('clr-dg-cell', 'PROCESSOR').first().click(); + cy.contains('button', 'Unregister Application').click(); + cy.contains('button', 'Unregister the application').click(); + cy.contains('Successfully removed app'); +}); diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/integration/utils.js b/.vib/spring-cloud-dataflow/cypress/cypress/integration/utils.js new file mode 100644 index 0000000000..21de22ebf9 --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/integration/utils.js @@ -0,0 +1 @@ +export let random = (Math.random() + 1).toString(36).substring(7); diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/support/commands.js b/.vib/spring-cloud-dataflow/cypress/cypress/support/commands.js new file mode 100644 index 0000000000..42dac69cdc --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/support/commands.js @@ -0,0 +1,26 @@ +const CLICK_DELAY = 800; +const TYPE_DELAY = 200; + +for (const command of ['click']) { + Cypress.Commands.overwrite(command, (originalFn, ...args) => { + const origVal = originalFn(...args); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(origVal); + }, CLICK_DELAY); + }); + }); +} + +for (const command of ['type']) { + Cypress.Commands.overwrite(command, (originalFn, ...args) => { + const origVal = originalFn(...args); + + return new Promise((resolve) => { + setTimeout(() => { + resolve(origVal); + }, TYPE_DELAY); + }); + }); +} diff --git a/.vib/spring-cloud-dataflow/cypress/cypress/support/index.js b/.vib/spring-cloud-dataflow/cypress/cypress/support/index.js new file mode 100644 index 0000000000..37a498fb5b --- /dev/null +++ b/.vib/spring-cloud-dataflow/cypress/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/.vib/spring-cloud-dataflow/goss/goss.yaml b/.vib/spring-cloud-dataflow/goss/goss.yaml new file mode 100644 index 0000000000..be85b3d8a4 --- /dev/null +++ b/.vib/spring-cloud-dataflow/goss/goss.yaml @@ -0,0 +1,22 @@ +file: + /opt/bitnami/spring-cloud-dataflow/conf/application.yml: + exists: true + filetype: symlink + mode: "0777" + owner: root + /bitnami/spring-cloud-dataflow: + exists: true + filetype: directory + mode: "0775" + owner: root +command: + user-id-test: + exec: if [ "$(id -u)" -eq 0 ]; then exit 1; fi + exit-status: 0 + stdout: [] + stderr: [] + java-test: + exec: java -version + exit-status: 0 + stdout: + stderr: [] diff --git a/.vib/spring-cloud-dataflow/vib-verify.json b/.vib/spring-cloud-dataflow/vib-verify.json index e7fb7b730e..270ef1be94 100644 --- a/.vib/spring-cloud-dataflow/vib-verify.json +++ b/.vib/spring-cloud-dataflow/vib-verify.json @@ -22,7 +22,7 @@ "url": "{SHA_ARCHIVE}", "path": "/bitnami/spring-cloud-dataflow" }, - "runtime_parameters": "InNlcnZlciI6CiAgInNlcnZpY2UiOgogICAgInBvcnQiOiA4MAogICAgInR5cGUiOiAiTG9hZEJhbGFuY2VyIg==", + "runtime_parameters": "c2VydmVyOgogIHNlcnZpY2U6CiAgICB0eXBlOiBMb2FkQmFsYW5jZXIKICAgIHBvcnQ6IDgwCg==", "target_platform": { "target_platform_id": "{VIB_ENV_TARGET_PLATFORM}", "size": { @@ -35,7 +35,9 @@ "action_id": "trivy", "params": { "threshold": "CRITICAL", - "vuln_type": ["OS"] + "vuln_type": [ + "OS" + ] } }, { @@ -44,6 +46,27 @@ "endpoint": "lb-spring-cloud-dataflow-server-http", "app_protocol": "HTTP" } + }, + { + "action_id": "goss", + "params": { + "resources": { + "path": "/.vib/spring-cloud-dataflow/goss" + }, + "remote": { + "workload": "deploy-spring-cloud-dataflow-server" + } + } + }, + { + "action_id": "cypress", + "params": { + "resources": { + "path": "/.vib/spring-cloud-dataflow/cypress" + }, + "endpoint": "lb-spring-cloud-dataflow-server-http", + "app_protocol": "HTTP" + } } ] }