diff --git a/cypress.json b/cypress.json index cc889431..f79065ba 100644 --- a/cypress.json +++ b/cypress.json @@ -2,6 +2,7 @@ "defaultCommandTimeout": 10000, "env": { "elasticsearch": "localhost:9200", - "kibana": "localhost:5601" + "kibana": "localhost:5601", + "security_enabled": false } } diff --git a/cypress/fixtures/sample_monitor_workflow_7.1.json b/cypress/fixtures/sample_monitor_workflow.json similarity index 77% rename from cypress/fixtures/sample_monitor_workflow_7.1.json rename to cypress/fixtures/sample_monitor_workflow.json index f67840b0..dbbaf62f 100644 --- a/cypress/fixtures/sample_monitor_workflow_7.1.json +++ b/cypress/fixtures/sample_monitor_workflow.json @@ -10,10 +10,10 @@ "inputs": [ { "search": { - "indices": [".*"], + "indices": ["alerting*"], "query": { "query": { - "term": { "monitor.type": "monitor" } + "match_all": {} } } } @@ -25,7 +25,7 @@ "severity": "3", "condition": { "script": { - "source": "ctx.results[0].hits.total.value > 1", + "source": "ctx.results[0].hits.total.value < 1", "lang": "painless" } }, diff --git a/cypress/integration/alert_spec.js b/cypress/integration/alert_spec.js index 3c31b504..e31ecbf9 100644 --- a/cypress/integration/alert_spec.js +++ b/cypress/integration/alert_spec.js @@ -15,10 +15,9 @@ import { PLUGIN_NAME } from '../support/constants'; import sampleMonitorWithAlwaysTrueTrigger from '../fixtures/sample_monitor_with_always_true_trigger'; -import sampleMonitorWorkflow from '../fixtures/sample_monitor_workflow_7.1.json'; +import sampleMonitorWorkflow from '../fixtures/sample_monitor_workflow'; -const SAMPLE_MONITOR_TO_BE_DELETED = 'sample_monitor_with_always_true_trigger'; -const SAMPLE_MONITOR_WORKFLOW = 'sample_monitor_workflow'; +const TESTING_INDEX = 'alerting_test'; describe('Alerts', () => { beforeEach(() => { @@ -34,7 +33,11 @@ describe('Alerts', () => { describe("can be in 'Active' state", () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); + // Generate a unique number in every test by getting a unix timestamp in milliseconds + Cypress.config('unique_number', `${Date.now()}`); + // Modify the monitor name to be unique + sampleMonitorWithAlwaysTrueTrigger.name += `-${Cypress.config('unique_number')}`; cy.createMonitor(sampleMonitorWithAlwaysTrueTrigger); }); @@ -45,6 +48,11 @@ describe('Alerts', () => { // Reload the page cy.reload(); + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); + // Confirm we can see one and only alert in Active state cy.get('tbody > tr').should(($tr) => { expect($tr, '1 row').to.have.length(1); @@ -55,16 +63,25 @@ describe('Alerts', () => { describe("can be in 'Acknowledged' state", () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); + Cypress.config('unique_number', `${Date.now()}`); + // Modify the monitor name to be unique + sampleMonitorWithAlwaysTrueTrigger.name += `-${Cypress.config('unique_number')}`; cy.createAndExecuteMonitor(sampleMonitorWithAlwaysTrueTrigger); }); it('by clicking the button in Dashboard', () => { + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); + //Confirm there is an active alert cy.contains('Active'); // Select checkbox for the existing alert - cy.get('input[data-test-subj^="checkboxSelectRow-"]').click({ force: true }); + // There may be multiple alerts in the cluster, first() is used to get the active alert + cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click({ force: true }); // Click Acknowledge button cy.get('button').contains('Acknowledge').click({ force: true }); @@ -76,70 +93,72 @@ describe('Alerts', () => { describe("can be in 'Completed' state", () => { before(() => { - cy.deleteAllIndices(); - cy.createMonitor(sampleMonitorWithAlwaysTrueTrigger); + cy.deleteAllMonitors(); + // Delete the target indices defined in 'sample_monitor_workflow.json' + cy.deleteIndexByName('alerting*'); + Cypress.config('unique_number', `${Date.now()}`); + // Modify the monitor name to be unique + sampleMonitorWorkflow.name += `-${Cypress.config('unique_number')}`; cy.createAndExecuteMonitor(sampleMonitorWorkflow); }); it('when the trigger condition is not met after met once', () => { - //Confirm there is an active alert - cy.contains('Active'); - - // Select checkbox for the existing alert - cy.get('input[data-test-subj^="checkboxSelectRow-"]').click({ force: true }); - - // Click Monitors button to route to Monitors tab - cy.get('button').contains('Monitors').click({ force: true }); + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); - // Type in monitor name in search box - cy.get(`input[type="search"]`).focus().type(SAMPLE_MONITOR_TO_BE_DELETED); - - // Confirm we filtered down to our one and only monitor - cy.get('tbody > tr').should(($tr) => { - expect($tr, '1 row').to.have.length(1); - expect($tr, 'item').to.contain(SAMPLE_MONITOR_TO_BE_DELETED); - }); - - // Select checkbox for the existing monitor - cy.get('input[data-test-subj^="checkboxSelectRow-"]').first().click({ force: true }); - - // Click Actions button to open the actions menu - cy.contains('Actions').click({ force: true }); - - // Click the Delete button - cy.contains('Delete').click({ force: true }); + // Confirm there is an active alert + cy.contains('Active'); - // Clear the text in the search box - cy.get(`input[type="search"]`).focus().clear(); + // The trigger condition is: there is no document in the indices 'alerting*' + // The following commands create a document in the index to complete the alert + // Create an index + cy.createIndexByName(TESTING_INDEX); - // Confirm we can see only one monitor in the list - cy.get('tbody > tr').should(($tr) => { - expect($tr, '1 row').to.have.length(1); - expect($tr, 'item').to.contain(SAMPLE_MONITOR_WORKFLOW); - }); + // Insert a document + cy.insertDocumentToIndex('test', 1, {}); // Wait for 1 minute cy.wait(60000); - // Click Dashboard button to route to Dashboard tab - cy.get('button').contains('Dashboard').click({ force: true }); + // Reload the page + cy.reload(); + + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); // Confirm we can see the alert is in 'Completed' state cy.contains('Completed'); }); + + after(() => { + // Delete the testing index + cy.deleteIndexByName(TESTING_INDEX); + }); }); describe("can be in 'Error' state", () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); // modify the JSON object to make an error alert when executing the monitor sampleMonitorWithAlwaysTrueTrigger.triggers[0].actions = [ { name: '', destination_id: '', message_template: { source: '' } }, ]; + Cypress.config('unique_number', `${Date.now()}`); + // Modify the monitor name to be unique + sampleMonitorWithAlwaysTrueTrigger.name += `-${Cypress.config('unique_number')}`; cy.createAndExecuteMonitor(sampleMonitorWithAlwaysTrueTrigger); }); it('by using a wrong destination', () => { + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); + // Confirm we can see the alert is in 'Error' state cy.contains('Error'); }); @@ -147,37 +166,40 @@ describe('Alerts', () => { describe("can be in 'Deleted' state", () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); + Cypress.config('unique_number', `${Date.now()}`); + // Modify the monitor name to be unique + sampleMonitorWithAlwaysTrueTrigger.name += `-${Cypress.config('unique_number')}`; cy.createAndExecuteMonitor(sampleMonitorWithAlwaysTrueTrigger); }); it('by deleting the monitor', () => { + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); + //Confirm there is an active alert cy.contains('Active'); - // Click Monitors button to route to Monitors tab - cy.get('button').contains('Monitors').click({ force: true }); - - // Confirm we can see a monitor in the list - cy.contains(SAMPLE_MONITOR_TO_BE_DELETED); - - // Select checkbox for the existing monitor - cy.get('input[data-test-subj^="checkboxSelectRow-"]').click({ force: true }); - - // Click Actions button to open the actions menu - cy.contains('Actions').click({ force: true }); - - // Click the Delete button - cy.contains('Delete').click({ force: true }); + // Delete all existing monitors + cy.deleteAllMonitors(); - // Confirm we can see an empty monitor list - cy.contains('There are no existing monitors'); + // Reload the page + cy.reload(); - // Click Dashboard button to route to Dashboard tab - cy.get('button').contains('Dashboard').click({ force: true }); + // Type in monitor name in search box to filter out the alert + cy.get(`input[type="search"]`) + .focus() + .type(`${Cypress.config('unique_number')}`); // Confirm we can see the alert is in 'Deleted' state cy.contains('Deleted'); }); }); + + after(() => { + // Delete all existing monitors + cy.deleteAllMonitors(); + }); }); diff --git a/cypress/integration/destination_spec.js b/cypress/integration/destination_spec.js index 5e100a52..ef1b9775 100644 --- a/cypress/integration/destination_spec.js +++ b/cypress/integration/destination_spec.js @@ -14,7 +14,7 @@ */ import { PLUGIN_NAME } from '../support/constants'; -import sampleDestination from '../fixtures/sample_destination_custom_webhook.json'; +import sampleDestination from '../fixtures/sample_destination_custom_webhook'; import sampleDestinationChime from '../fixtures/sample_destination_chime'; const SAMPLE_DESTINATION = 'sample_destination'; @@ -36,7 +36,7 @@ describe('Destinations', () => { describe('can be created', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllDestinations(); }); it('with a custom webhook', () => { @@ -65,7 +65,7 @@ describe('Destinations', () => { describe('can be updated', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllDestinations(); cy.createDestination(sampleDestination); }); @@ -77,7 +77,11 @@ describe('Destinations', () => { cy.get('button').contains('Edit').click({ force: true }); // Wait for input to load and then type in the destination name - cy.get('input[name="name"]').focus().clear().type(UPDATED_DESTINATION, { force: true }); + // should() is used to wait for input loading before clearing + cy.get('input[name="name"]') + .should('have.value', SAMPLE_DESTINATION) + .clear() + .type(UPDATED_DESTINATION, { force: true }); // Click the create button cy.get('button').contains('Update').click({ force: true }); @@ -89,7 +93,7 @@ describe('Destinations', () => { describe('can be deleted', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllDestinations(); cy.createDestination(sampleDestination); }); @@ -110,7 +114,7 @@ describe('Destinations', () => { describe('can be searched', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllDestinations(); // Create 21 destinations so that a monitor will not appear in the first page for (let i = 0; i < 20; i++) { cy.createDestination(sampleDestination); @@ -135,4 +139,9 @@ describe('Destinations', () => { }); }); }); + + after(() => { + // Delete all existing destinations + cy.deleteAllDestinations(); + }); }); diff --git a/cypress/integration/monitor_spec.js b/cypress/integration/monitor_spec.js index 9b2f12d6..aa49ef0b 100644 --- a/cypress/integration/monitor_spec.js +++ b/cypress/integration/monitor_spec.js @@ -38,7 +38,7 @@ describe('Monitors', () => { describe('can be created', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); }); it('defining by extraction query', () => { @@ -73,7 +73,7 @@ describe('Monitors', () => { describe('can be updated', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); cy.createMonitor(sampleMonitor); }); @@ -88,7 +88,10 @@ describe('Monitors', () => { cy.contains('Edit').click({ force: true }); // Wait for input to load and then type in the new monitor name - cy.get('input[name="name"]').focus().clear().type(UPDATED_MONITOR, { force: true }); + cy.get('input[name="name"]') + .should('have.value', SAMPLE_MONITOR) + .clear() + .type(UPDATED_MONITOR, { force: true }); // Click Update button cy.get('button').contains('Update').last().click({ force: true }); @@ -106,7 +109,7 @@ describe('Monitors', () => { describe('can be deleted', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); cy.createMonitor(sampleMonitor); }); @@ -130,7 +133,7 @@ describe('Monitors', () => { describe('can be searched', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); // Create 21 monitors so that a monitor will not appear in the first page for (let i = 0; i < 20; i++) { cy.createMonitor(sampleMonitor); @@ -158,7 +161,8 @@ describe('Monitors', () => { describe('can have triggers', () => { before(() => { - cy.deleteAllIndices(); + cy.deleteAllMonitors(); + cy.deleteAllDestinations(); cy.createMonitor(sampleMonitor); }); @@ -237,4 +241,10 @@ describe('Monitors', () => { cy.contains(SAMPLE_ACTION); }); }); + + after(() => { + // Delete all existing monitors and destinations + cy.deleteAllMonitors(); + cy.deleteAllDestinations(); + }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 08a05549..9a239642 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -const { API, INDEX } = require('./constants'); +const { API, ADMIN_AUTH } = require('./constants'); // *********************************************** // This example commands.js shows you how to @@ -41,33 +41,54 @@ const { API, INDEX } = require('./constants'); // -- This will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) -Cypress.Commands.add('deleteAllIndices', () => { - cy.request('DELETE', `${Cypress.env('elasticsearch')}/*?expand_wildcards=all`); +Cypress.Commands.overwrite('visit', (originalFn, url, options) => { + // Add the basic auth header when security enabled in the Elasticsearch cluster + // https://github.com/cypress-io/cypress/issues/1288 + if (Cypress.env('security_enabled')) { + if (options) { + options.auth = ADMIN_AUTH; + } else { + options = { auth: ADMIN_AUTH }; + } + return originalFn(url, options); + } else { + return originalFn(url, options); + } }); -Cypress.Commands.add('deleteAllMonitors', () => { - const body = { - query: { exists: { field: 'monitor' } }, - }; - cy.request( - 'POST', - `${Cypress.env('elasticsearch')}/${INDEX.OPENDISTRO_ALERTING_CONFIG}/_delete_by_query`, - body - ); +// Be able to add default options to cy.request(), https://github.com/cypress-io/cypress/issues/726 +Cypress.Commands.overwrite('request', (originalFn, ...args) => { + let defaults = {}; + // Add the basic authentication header when security enabled in the Elasticsearch cluster + if (Cypress.env('security_enabled')) { + defaults.auth = ADMIN_AUTH; + } + + let options = {}; + if (typeof args[0] === 'object' && args[0] !== null) { + options = Object.assign({}, args[0]); + } else if (args.length === 1) { + [options.url] = args; + } else if (args.length === 2) { + [options.method, options.url] = args; + } else if (args.length === 3) { + [options.method, options.url, options.body] = args; + } + + return originalFn(Object.assign({}, defaults, options)); }); -Cypress.Commands.add('createMonitor', monitorJSON => { +Cypress.Commands.add('createMonitor', (monitorJSON) => { cy.request('POST', `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}`, monitorJSON); }); -Cypress.Commands.add('createDestination', destinationJSON => { +Cypress.Commands.add('createDestination', (destinationJSON) => { cy.request('POST', `${Cypress.env('elasticsearch')}${API.DESTINATION_BASE}`, destinationJSON); }); -Cypress.Commands.add('createAndExecuteMonitor', monitorJSON => { +Cypress.Commands.add('createAndExecuteMonitor', (monitorJSON) => { cy.request('POST', `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}`, monitorJSON).then( - response => { - // response.body is automatically serialized into JSON + (response) => { cy.request( 'POST', `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}/${response.body._id}/_execute` @@ -75,3 +96,89 @@ Cypress.Commands.add('createAndExecuteMonitor', monitorJSON => { } ); }); + +Cypress.Commands.add('deleteMonitorByName', (monitorName) => { + const body = { + query: { + match: { + 'monitor.name': { + query: monitorName, + operator: 'and', + }, + }, + }, + }; + cy.request('GET', `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}/_search`, body).then( + (response) => { + cy.request( + 'DELETE', + `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}/${response.body.hits.hits[0]._id}` + ); + } + ); +}); + +Cypress.Commands.add('deleteAllMonitors', () => { + const body = { + size: 200, + query: { + exists: { + field: 'monitor', + }, + }, + }; + cy.request({ + method: 'GET', + url: `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}/_search`, + failOnStatusCode: false, // In case there is no alerting config index in cluster, where the status code is 404 + body, + }).then((response) => { + if (response.status === 200) { + for (let i = 0; i < response.body.hits.total.value; i++) { + cy.request( + 'DELETE', + `${Cypress.env('elasticsearch')}${API.MONITOR_BASE}/${response.body.hits.hits[i]._id}` + ); + } + } else { + cy.log('Failed to get all monitors.', response); + } + }); +}); + +Cypress.Commands.add('deleteAllDestinations', () => { + cy.request({ + method: 'GET', + url: `${Cypress.env('elasticsearch')}${API.DESTINATION_BASE}?size=200`, + failOnStatusCode: false, // In case there is no alerting config index in cluster, where the status code is 404 + }).then((response) => { + if (response.status === 200) { + for (let i = 0; i < response.body.totalDestinations; i++) { + cy.request( + 'DELETE', + `${Cypress.env('elasticsearch')}${API.DESTINATION_BASE}/${ + response.body.destinations[i].id + }` + ); + } + } else { + cy.log('Failed to get all destinations.', response); + } + }); +}); + +Cypress.Commands.add('createIndexByName', (indexName) => { + cy.request('PUT', `${Cypress.env('elasticsearch')}/${indexName}`); +}); + +Cypress.Commands.add('deleteIndexByName', (indexName) => { + cy.request('DELETE', `${Cypress.env('elasticsearch')}/${indexName}`); +}); + +Cypress.Commands.add('insertDocumentToIndex', (indexName, documentId, documentBody) => { + cy.request( + 'PUT', + `${Cypress.env('elasticsearch')}/${indexName}/_doc/${documentId}`, + documentBody + ); +}); diff --git a/cypress/support/constants.js b/cypress/support/constants.js index 3c8f3fa7..8fc0868d 100644 --- a/cypress/support/constants.js +++ b/cypress/support/constants.js @@ -25,3 +25,8 @@ export const API = { }; export const PLUGIN_NAME = 'opendistro-alerting'; + +export const ADMIN_AUTH = { + username: 'admin', + password: 'admin', +}; diff --git a/cypress/support/index.js b/cypress/support/index.js index 65ac9482..6bb32158 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -42,3 +42,8 @@ Cypress.on('uncaught:exception', (err) => { return false; } }); + +// Switch the base URL of Elasticsearch when security enabled in the cluster +if (Cypress.env('security_enabled')) { + Cypress.env('elasticsearch', 'https://localhost:9200'); +}