From cc1d964282224a6b3a6b7016a1f2c6692abb1253 Mon Sep 17 00:00:00 2001 From: ksjaay Date: Sun, 13 Oct 2024 22:00:10 +0100 Subject: [PATCH] Adds notification tests and updates server middleware tests --- cypress.config.js | 1 + shared/validators/notifications/discord.js | 10 +- shared/validators/notifications/slack.js | 11 +- shared/validators/notifications/telegram.js | 6 +- shared/validators/notifications/webhook.js | 8 +- test/e2e/monitor.test.js | 115 ----------- test/e2e/monitor/http.test.js | 65 ++++++ test/e2e/monitor/tcp.test.js | 65 ++++++ test/e2e/notification/discord.test.js | 101 ++++++++++ test/e2e/notification/slack.test.js | 105 ++++++++++ test/e2e/notification/telegram.test.js | 105 ++++++++++ test/e2e/notification/webhooks.test.js | 105 ++++++++++ test/e2e/setup/fixtures/monitor.json | 188 +++++++++++++----- .../setup/fixtures/notifications/discord.json | 37 ++++ .../setup/fixtures/notifications/slack.json | 53 +++++ .../fixtures/notifications/telegram.json | 37 ++++ .../fixtures/notifications/webhooks.json | 27 +++ test/e2e/setup/support/commands.js | 114 +++++++++-- test/server/middleware/monitor/add.test.js | 10 +- test/server/middleware/monitor/edit.test.js | 14 +- .../middleware/user/update/password.test.js | 4 +- 21 files changed, 958 insertions(+), 223 deletions(-) delete mode 100644 test/e2e/monitor.test.js create mode 100644 test/e2e/monitor/http.test.js create mode 100644 test/e2e/monitor/tcp.test.js create mode 100644 test/e2e/notification/discord.test.js create mode 100644 test/e2e/notification/slack.test.js create mode 100644 test/e2e/notification/telegram.test.js create mode 100644 test/e2e/notification/webhooks.test.js create mode 100644 test/e2e/setup/fixtures/notifications/discord.json create mode 100644 test/e2e/setup/fixtures/notifications/slack.json create mode 100644 test/e2e/setup/fixtures/notifications/telegram.json create mode 100644 test/e2e/setup/fixtures/notifications/webhooks.json diff --git a/cypress.config.js b/cypress.config.js index 47b42a5..dea74ba 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -11,5 +11,6 @@ export default defineConfig({ videosFolder: 'test/e2e/setup/videos', downloadsFolder: 'test/e2e/setup/downloads', supportFile: 'test/e2e/setup/support/e2e.js', + experimentalRunAllSpecs: true, }, }); diff --git a/shared/validators/notifications/discord.js b/shared/validators/notifications/discord.js index 7720ce4..667fe9a 100644 --- a/shared/validators/notifications/discord.js +++ b/shared/validators/notifications/discord.js @@ -1,14 +1,12 @@ // messageType: type of message for discord webhook (basic, pretty, nerdy) // friendlyName: friendly name for discord webhook -// textMessage: text message for discord webhook (optional) // token: url for discord webhook // username: username for discord webhook (optional) import { NotificationValidatorError } from '../../utils/errors.js'; -const friendlyNameRegex = /^[a-zA-Z0-9_ ]+$/; +const friendlyNameRegex = /^[a-zA-Z0-9_-]+$/; const messageTypes = ['basic', 'pretty', 'nerdy']; -const textMessageRegex = /^[a-zA-Z0-9_ ]+$/; const tokenRegex = /^https:\/\/discord.com\/api\/webhooks\/[0-9]+\/[0-9a-zA-Z_.-]+$/; const usernameRegex = /^[a-zA-Z0-9_]{1,32}$/; @@ -23,7 +21,7 @@ const Discord = ({ if (friendlyNameRegex && !friendlyNameRegex.test(friendlyName)) { throw new NotificationValidatorError( 'friendlyName', - 'Invalid Friendly Name. Must be alphanumeric, Spaces, and underscores only.' + 'Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only.' ); } @@ -31,10 +29,6 @@ const Discord = ({ throw new NotificationValidatorError('messageType', 'Invalid Message Type'); } - if (textMessage && !textMessageRegex.test(textMessage)) { - throw new NotificationValidatorError('textMessage', 'Invalid Text Message'); - } - if (!tokenRegex.test(token)) { throw new NotificationValidatorError( 'token', diff --git a/shared/validators/notifications/slack.js b/shared/validators/notifications/slack.js index c92ad9a..85a34c6 100644 --- a/shared/validators/notifications/slack.js +++ b/shared/validators/notifications/slack.js @@ -7,10 +7,9 @@ import { NotificationValidatorError } from '../../utils/errors.js'; -const channelRegex = /^#[a-zA-Z0-9_]{1,32}$/; -const friendlyNameRegex = /^[a-zA-Z0-9_ ]+$/; +const channelRegex = /^[a-z0-9][a-z0-9_-]{0,79}$/; +const friendlyNameRegex = /^[a-zA-Z0-9_-]+$/; const messageTypes = ['basic', 'pretty', 'nerdy']; -const textMessageRegex = /^[a-zA-Z0-9_ ]+$/; const tokenRegex = /^https:\/\/hooks.slack.com\/services\/[0-9a-zA-Z]+\/[0-9a-zA-Z]+\/[0-9a-zA-Z]+$/; const usernameRegex = /^[a-zA-Z0-9_]{1,32}$/; @@ -26,7 +25,7 @@ const Slack = ({ if (friendlyNameRegex && !friendlyNameRegex.test(friendlyName)) { throw new NotificationValidatorError( 'friendlyName', - 'Invalid Friendly Name. Must be alphanumeric, Spaces, and underscores only.' + 'Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only.' ); } @@ -38,10 +37,6 @@ const Slack = ({ throw new NotificationValidatorError('messageType', 'Invalid Message Type'); } - if (textMessage && !textMessageRegex.test(textMessage)) { - throw new NotificationValidatorError('textMessage', 'Invalid Text Message'); - } - if (!tokenRegex.test(token)) { throw new NotificationValidatorError('token', 'Invalid Slack Webhook URL'); } diff --git a/shared/validators/notifications/telegram.js b/shared/validators/notifications/telegram.js index a7f9b48..642f49a 100644 --- a/shared/validators/notifications/telegram.js +++ b/shared/validators/notifications/telegram.js @@ -8,7 +8,7 @@ import { NotificationValidatorError } from '../../utils/errors.js'; const chatIdRegex = /^[0-9]+$/; -const friendlyNameRegex = /^[a-zA-Z0-9_ ]+$/; +const friendlyNameRegex = /^[a-zA-Z0-9_-]+$/; const messageTypes = ['basic', 'pretty', 'nerdy']; const tokenRegex = /^[a-zA-Z0-9_]{1,32}$/; @@ -23,7 +23,7 @@ const Telegram = ({ if (friendlyNameRegex && !friendlyNameRegex.test(friendlyName)) { throw new NotificationValidatorError( 'friendlyName', - 'Invalid Friendly Name. Must be alphanumeric, Spaces, and underscores only.' + 'Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only.' ); } @@ -52,7 +52,7 @@ const Telegram = ({ if (!tokenRegex.test(token)) { throw new NotificationValidatorError( 'token', - 'Invalid Telegram Webhook URL' + 'Invalid Telegram Bot Token' ); } diff --git a/shared/validators/notifications/webhook.js b/shared/validators/notifications/webhook.js index 20753b7..5428b8f 100644 --- a/shared/validators/notifications/webhook.js +++ b/shared/validators/notifications/webhook.js @@ -7,7 +7,7 @@ import { NotificationValidatorError } from '../../utils/errors.js'; -const friendlyNameRegex = /^[a-zA-Z0-9_ ]+$/; +const friendlyNameRegex = /^[a-zA-Z0-9_-]+$/; const messageTypes = ['basic', 'pretty', 'nerdy']; const requestTypes = ['application/json', 'form-data']; const tokenRegex = @@ -33,14 +33,14 @@ const Webhook = ({ if (friendlyNameRegex && !friendlyNameRegex.test(friendlyName)) { throw new NotificationValidatorError( 'friendlyName', - 'Invalid Friendly Name. Must be alphanumeric, Spaces, and underscores only.' + 'Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only.' ); } if (showAdditionalHeaders && !isJson(additionalHeaders)) { throw new NotificationValidatorError( 'additionalHeaders', - 'Invalid Additional Headers' + 'Invalid Additional Headers Format' ); } @@ -48,7 +48,7 @@ const Webhook = ({ throw new NotificationValidatorError('messageType', 'Invalid Message Type'); } - if (!tokenRegex.test(token)) { + if (!tokenRegex.test(token)) { throw new NotificationValidatorError('token', 'Invalid Webhook URL'); } diff --git a/test/e2e/monitor.test.js b/test/e2e/monitor.test.js deleted file mode 100644 index adf7989..0000000 --- a/test/e2e/monitor.test.js +++ /dev/null @@ -1,115 +0,0 @@ -import loginDetails from './setup/fixtures/login.json'; -import monitorDetails from './setup/fixtures/monitor.json'; - -describe('Monitor', () => { - const { name, type, url, method, interval, retryInterval, timeout } = - monitorDetails; - - const { value: monitorName } = name; - - context('create a monitor', () => { - beforeEach(() => { - const { email, password } = loginDetails.ownerUser; - - cy.clearCookies(); - cy.loginUser(email, password); - }); - - it('Go to next page without adding Name and Type', () => { - cy.createMonitor(); - - cy.get('[id="Next"]').click(); - - cy.equals(name.error.id, name.error.value); - cy.equals(type.error.id, type.error.value); - }); - - it('Enter a valid name/type and go to next page', () => { - cy.createMonitor(name, type); - }); - - it('Go to next page without adding URL and Method', () => { - cy.createMonitor(name, type); - - cy.get('[id="Next"]').click(); - - cy.equals(url.error.id, url.error.value); - cy.equals(method.error.id, method.error.value); - }); - - it('Enter a valid URL/Method and go to next page', () => { - cy.createMonitor(name, type, url, method); - }); - - it('Enter invalid time for interval, retry interval and request timeout', () => { - cy.createMonitor(name, type, url, method); - - cy.typeText(interval.id, interval.invalidValue); - cy.typeText(retryInterval.id, retryInterval.invalidValue); - cy.typeText(timeout.id, timeout.invalidValue); - - cy.get('[id="Submit"]').click(); - - cy.equals(interval.error.id, interval.error.value); - cy.equals(retryInterval.error.id, retryInterval.error.value); - cy.equals(timeout.error.id, timeout.error.value); - }); - - it('Enter valid time for interval, retry interval and request timeout and go to next page', () => { - cy.createMonitor( - name, - type, - url, - method, - interval, - retryInterval, - timeout - ); - }); - }); - - context('edit a monitor', () => { - before(() => { - const { email, password } = loginDetails.ownerUser; - - cy.clearCookies(); - cy.loginUser(email, password); - cy.visit('/'); - }); - - it('Edit monitor name', () => { - cy.get(`[id="monitor-${monitorName}"]`).click(); - - cy.get('[id="monitor-edit-button"]').click(); - - cy.typeText(name.id, '-Edited'); - - cy.get('[id="Next"]').click(); - cy.get('[id="Next"]').click(); - - cy.get('[id="Submit"]').click(); - - cy.equals('[id="monitor-view-menu-name"]', `${name.value}-Edited`); - }); - }); - - context('delete a monitor', () => { - before(() => { - const { email, password } = loginDetails.ownerUser; - - cy.clearCookies(); - cy.loginUser(email, password); - cy.visit('/'); - }); - - it('Delete monitor', () => { - cy.get(`[id="monitor-${monitorName}-Edited"]`).click(); - - cy.get('[id="monitor-delete-button"]').click(); - - cy.get('[id="monitor-delete-confirm-button"]').click(); - - cy.get(`[id="monitor-${monitorName}-Edited"]`).should('not.exist'); - }); - }); -}); diff --git a/test/e2e/monitor/http.test.js b/test/e2e/monitor/http.test.js new file mode 100644 index 0000000..c3d2620 --- /dev/null +++ b/test/e2e/monitor/http.test.js @@ -0,0 +1,65 @@ +import loginDetails from '../setup/fixtures/login.json'; +import monitorDetails from '../setup/fixtures/monitor.json'; + +describe('Monitor HTTP - Advance', () => { + context('create a monitor with basic information', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('should show errors for invalid values and create a monitor with valid values', () => { + cy.createMonitor(monitorDetails.http); + }); + }); + + context('edit a monitor', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('Edit monitor name', () => { + cy.get(`[id="monitor-${monitorDetails.http.name.value}"]`).click(); + + cy.get('[id="monitor-edit-button"]').click(); + + cy.typeText(monitorDetails.http.name.id, '-Edited'); + + cy.get('[id="monitor-create-button"]').click(); + + cy.equals( + '[id="monitor-view-menu-name"]', + `${monitorDetails.http.name.value}-Edited` + ); + }); + }); + + context('delete a monitor', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('Delete monitor', () => { + cy.get(`[id="monitor-${monitorDetails.http.name.value}-Edited"]`).click(); + + cy.get('[id="monitor-delete-button"]').click(); + + cy.get('[id="monitor-delete-confirm-button"]').click(); + + cy.get(`[id="monitor-${monitorDetails.http.name.value}-Edited"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/monitor/tcp.test.js b/test/e2e/monitor/tcp.test.js new file mode 100644 index 0000000..3665ba5 --- /dev/null +++ b/test/e2e/monitor/tcp.test.js @@ -0,0 +1,65 @@ +import loginDetails from '../setup/fixtures/login.json'; +import monitorDetails from '../setup/fixtures/monitor.json'; + +describe('Monitor TCP - Advance', () => { + context('create a monitor with basic information', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('should show errors for invalid values and create a monitor with valid values', () => { + cy.createMonitor(monitorDetails.tcp); + }); + }); + + context('edit a monitor', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('Edit monitor name', () => { + cy.get(`[id="monitor-${monitorDetails.tcp.name.value}"]`).click(); + + cy.get('[id="monitor-edit-button"]').click(); + + cy.typeText(monitorDetails.tcp.name.id, '-Edited'); + + cy.get('[id="monitor-create-button"]').click(); + + cy.equals( + '[id="monitor-view-menu-name"]', + `${monitorDetails.tcp.name.value}-Edited` + ); + }); + }); + + context('delete a monitor', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/'); + }); + + it('should delete a monitor', () => { + cy.get(`[id="monitor-${monitorDetails.tcp.name.value}-Edited"]`).click(); + + cy.get('[id="monitor-delete-button"]').click(); + + cy.get('[id="monitor-delete-confirm-button"]').click(); + + cy.get(`[id="monitor-${monitorDetails.tcp.name.value}-Edited"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/notification/discord.test.js b/test/e2e/notification/discord.test.js new file mode 100644 index 0000000..5a1a84e --- /dev/null +++ b/test/e2e/notification/discord.test.js @@ -0,0 +1,101 @@ +import loginDetails from '../setup/fixtures/login.json'; +import discordNotification from '../setup/fixtures/notifications/discord.json'; + +describe('Notification - Discord', () => { + context('create a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show invalid errors and create notification', () => { + cy.createNotification(discordNotification); + }); + }); + + context('edit a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show error if invalid name is given', () => { + cy.get( + `[id="notification-configure-${discordNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(discordNotification.friendlyName.id, '{}[]||<>'); + cy.get('[id="notification-create-button"]').click(); + + cy.equals( + discordNotification.friendlyName.error.id, + discordNotification.friendlyName.error.value + ); + }); + + it('should change the name and save', () => { + cy.get( + `[id="notification-configure-${discordNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(discordNotification.friendlyName.id, 'Test'); + cy.get('[id="notification-create-button"]').click(); + + cy.get(discordNotification.friendlyName.error.id).should('not.exist'); + }); + }); + + context('disable a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + // Disable by clicking the three dots and then click disable + + it('should disable a notification', () => { + const friendlyName = `${discordNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).should( + 'have.text', + 'Enable' + ); + }); + }); + + context('delete a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should delete a notification', () => { + const friendlyName = `${discordNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-delete-${friendlyName}"]`).click(); + + cy.get(`[id="notification-delete-confirm"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/notification/slack.test.js b/test/e2e/notification/slack.test.js new file mode 100644 index 0000000..b25cbb1 --- /dev/null +++ b/test/e2e/notification/slack.test.js @@ -0,0 +1,105 @@ +import loginDetails from '../setup/fixtures/login.json'; +import slackNotification from '../setup/fixtures/notifications/slack.json'; + +describe('Notification - Slack', () => { + context('create a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show invalid errors and create notification', () => { + cy.createNotification(slackNotification); + + cy.get( + `[id="notification-configure-${slackNotification.friendlyName.value}"]` + ).should('exist'); + }); + }); + + context('edit a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show error if invalid name is given', () => { + cy.get( + `[id="notification-configure-${slackNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(slackNotification.friendlyName.id, '{}[]||<>'); + cy.get('[id="notification-create-button"]').click(); + + cy.equals( + slackNotification.friendlyName.error.id, + slackNotification.friendlyName.error.value + ); + }); + + it('should change the name and save', () => { + cy.get( + `[id="notification-configure-${slackNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(slackNotification.friendlyName.id, 'Test'); + cy.get('[id="notification-create-button"]').click(); + + cy.get(slackNotification.friendlyName.error.id).should('not.exist'); + }); + }); + + context('disable a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + // Disable by clicking the three dots and then click disable + + it('should disable a notification', () => { + const friendlyName = `${slackNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).should( + 'have.text', + 'Enable' + ); + }); + }); + + context('delete a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should delete a notification', () => { + const friendlyName = `${slackNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-delete-${friendlyName}"]`).click(); + + cy.get(`[id="notification-delete-confirm"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/notification/telegram.test.js b/test/e2e/notification/telegram.test.js new file mode 100644 index 0000000..564bd96 --- /dev/null +++ b/test/e2e/notification/telegram.test.js @@ -0,0 +1,105 @@ +import loginDetails from '../setup/fixtures/login.json'; +import telegramNotification from '../setup/fixtures/notifications/telegram.json'; + +describe('Notification - Telegram', () => { + context('create a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show invalid errors and create notification', () => { + cy.createNotification(telegramNotification); + + cy.get( + `[id="notification-configure-${telegramNotification.friendlyName.value}"]` + ).should('exist'); + }); + }); + + context('edit a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show error if invalid name is given', () => { + cy.get( + `[id="notification-configure-${telegramNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(telegramNotification.friendlyName.id, '{}[]||<>'); + cy.get('[id="notification-create-button"]').click(); + + cy.equals( + telegramNotification.friendlyName.error.id, + telegramNotification.friendlyName.error.value + ); + }); + + it('should change the name and save', () => { + cy.get( + `[id="notification-configure-${telegramNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(telegramNotification.friendlyName.id, 'Test'); + cy.get('[id="notification-create-button"]').click(); + + cy.get(telegramNotification.friendlyName.error.id).should('not.exist'); + }); + }); + + context('disable a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + // Disable by clicking the three dots and then click disable + + it('should disable a notification', () => { + const friendlyName = `${telegramNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).should( + 'have.text', + 'Enable' + ); + }); + }); + + context('delete a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should delete a notification', () => { + const friendlyName = `${telegramNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-delete-${friendlyName}"]`).click(); + + cy.get(`[id="notification-delete-confirm"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/notification/webhooks.test.js b/test/e2e/notification/webhooks.test.js new file mode 100644 index 0000000..3f36f7e --- /dev/null +++ b/test/e2e/notification/webhooks.test.js @@ -0,0 +1,105 @@ +import loginDetails from '../setup/fixtures/login.json'; +import webhookNotification from '../setup/fixtures/notifications/webhooks.json'; + +describe('Notification - Telegram', () => { + context('create a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show invalid errors and create notification', () => { + cy.createNotification(webhookNotification); + + cy.get( + `[id="notification-configure-${webhookNotification.friendlyName.value}"]` + ).should('exist'); + }); + }); + + context('edit a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should show error if invalid name is given', () => { + cy.get( + `[id="notification-configure-${webhookNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(webhookNotification.friendlyName.id, '{}[]||<>'); + cy.get('[id="notification-create-button"]').click(); + + cy.equals( + webhookNotification.friendlyName.error.id, + webhookNotification.friendlyName.error.value + ); + }); + + it('should change the name and save', () => { + cy.get( + `[id="notification-configure-${webhookNotification.friendlyName.value}"]` + ).click(); + + cy.typeText(webhookNotification.friendlyName.id, 'Test'); + cy.get('[id="notification-create-button"]').click(); + + cy.get(webhookNotification.friendlyName.error.id).should('not.exist'); + }); + }); + + context('disable a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + // Disable by clicking the three dots and then click disable + + it('should disable a notification', () => { + const friendlyName = `${webhookNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-toggle-${friendlyName}"]`).should( + 'have.text', + 'Enable' + ); + }); + }); + + context('delete a notification', () => { + beforeEach(() => { + const { email, password } = loginDetails.ownerUser; + + cy.clearCookies(); + cy.loginUser(email, password); + cy.visit('/notifications'); + }); + + it('should delete a notification', () => { + const friendlyName = `${webhookNotification.friendlyName.value}Test`; + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).click(); + cy.get(`[id="notification-delete-${friendlyName}"]`).click(); + + cy.get(`[id="notification-delete-confirm"]`).click(); + + cy.get(`[id="notification-dropdown-${friendlyName}"]`).should( + 'not.exist' + ); + }); + }); +}); diff --git a/test/e2e/setup/fixtures/monitor.json b/test/e2e/setup/fixtures/monitor.json index 961fa03..a395ac7 100644 --- a/test/e2e/setup/fixtures/monitor.json +++ b/test/e2e/setup/fixtures/monitor.json @@ -1,61 +1,139 @@ { - "name": { - "id": "[id=\"input-name\"]", - "value": "test-monitor", - "error": { - "id": "[id=\"text-input-error-input-name\"]", - "value": "Please enter a valid name. Only letters, numbers and - are allowed." + "http": { + "name": { + "id": "[id=\"input-name\"]", + "value": "test-monitor", + "type": "text", + "invalidValue": "@#${}[]||<>", + "error": { + "id": "[id=\"text-input-error-input-name\"]", + "value": "Please enter a valid name. Only letters, numbers and - are allowed." + } + }, + "type": { + "id": "[id=\"type-dropdown\"]", + "value": "[id=\"type-http\"]", + "type": "dropdown" + }, + "url": { + "id": "[id=\"input-url\"]", + "value": "http://lunalytics.xyz/api/status", + "type": "text", + "invalidValue": "@#${}[]||<>", + "error": { + "id": "[id=\"text-input-error-input-url\"]", + "value": "Please enter a valid URL." + } + }, + "advance": { + "id": "[id=\"monitor-advanced-settings\"]", + "type": "click" + }, + "method": { + "id": "[id=\"http-method-dropdown\"]", + "value": "[id=\"http-method-GET\"]", + "type": "dropdown" + }, + "interval": { + "id": "[id=\"input-interval\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-interval\"]", + "value": "Please enter a valid interval. Interval should be between 20 and 600 seconds." + } + }, + "retryInterval": { + "id": "[id=\"input-retry-interval\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-retry-interval\"]", + "value": "Please enter a valid retry interval. Retry interval should be between 20 and 600 seconds." + } + }, + "timeout": { + "id": "[id=\"input-request-timeout\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-request-timeout\"]", + "value": "Please enter a valid request timeout. Request timeout should be between 20 and 600 seconds." + } } }, - "type": { - "id": "[id=\"type-dropdown\"]", - "value": "[id=\"type-http\"]", - "error": { - "id": "[id=\"text-input-error-input-type\"]", - "value": "Please select a valid monitor type." - } - }, - "url": { - "id": "[id=\"input-url\"]", - "value": "http://lunalytics.xyz/api/status", - "error": { - "id": "[id=\"text-input-error-input-url\"]", - "value": "Please enter a valid URL." - } - }, - "method": { - "id": "[id=\"http-method-dropdown\"]", - "value": "[id=\"http-method-GET\"]", - "error": { - "id": "[id=\"text-input-http-method-error\"]", - "value": "Please select a valid method." - } - }, - "interval": { - "id": "[id=\"input-interval\"]", - "value": "{backspace} {backspace} 45", - "invalidValue": 10, - "error": { - "id": "[id=\"text-input-error-input-interval\"]", - "value": "Please enter a valid interval. Interval should be between 20 and 600 seconds." - } - }, - "retryInterval": { - "id": "[id=\"input-retry-interval\"]", - "value": "{backspace} {backspace} 45", - "invalidValue": 10, - "error": { - "id": "[id=\"text-input-error-input-retry-interval\"]", - "value": "Please enter a valid retry interval. Retry interval should be between 20 and 600 seconds." - } - }, - "timeout": { - "id": "[id=\"input-request-timeout\"]", - "value": "{backspace} {backspace} 45", - "invalidValue": 10, - "error": { - "id": "[id=\"text-input-error-input-request-timeout\"]", - "value": "Please enter a valid request timeout. Request timeout should be between 20 and 600 seconds." + "tcp": { + "name": { + "id": "[id=\"input-name\"]", + "value": "test-monitor", + "type": "text", + "invalidValue": "@#${}[]||<>", + "error": { + "id": "[id=\"text-input-error-input-name\"]", + "value": "Please enter a valid name. Only letters, numbers and - are allowed." + } + }, + "type": { + "id": "[id=\"type-dropdown\"]", + "value": "[id=\"type-tcp\"]", + "type": "dropdown" + }, + "host": { + "id": "[id=\"input-host\"]", + "value": "127.0.0.1", + "type": "text", + "invalidValue": "@#${}[]||<>", + "error": { + "id": "[id=\"text-input-error-input-host\"]", + "value": "Please enter a valid host (Only IPv4 is valid)." + } + }, + "port": { + "id": "[id=\"input-port\"]", + "value": "2308", + "type": "text", + "invalidValue": "2308999", + "error": { + "id": "[id=\"text-input-error-input-port\"]", + "value": "Please enter a valid port." + } + }, + "advance": { + "id": "[id=\"monitor-advanced-settings\"]", + "type": "click" + }, + "interval": { + "id": "[id=\"input-interval\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-interval\"]", + "value": "Please enter a valid interval. Interval should be between 20 and 600 seconds." + } + }, + "retryInterval": { + "id": "[id=\"input-retry-interval\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-retry-interval\"]", + "value": "Please enter a valid retry interval. Retry interval should be between 20 and 600 seconds." + } + }, + "timeout": { + "id": "[id=\"input-request-timeout\"]", + "value": "{backspace} {backspace} 45", + "invalidValue": 10, + "type": "text", + "error": { + "id": "[id=\"text-input-error-input-request-timeout\"]", + "value": "Please enter a valid request timeout. Request timeout should be between 20 and 600 seconds." + } } } } diff --git a/test/e2e/setup/fixtures/notifications/discord.json b/test/e2e/setup/fixtures/notifications/discord.json new file mode 100644 index 0000000..dcdff5a --- /dev/null +++ b/test/e2e/setup/fixtures/notifications/discord.json @@ -0,0 +1,37 @@ +{ + "friendlyName": { + "id": "[id=\"friendly-name\"]", + "value": "Discord", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-friendly-name\"]", + "value": "Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only." + } + }, + "url": { + "id": "[id=\"webhook-url\"]", + "value": "https://discord.com/api/webhooks/082399/lunalytics", + "invalidValue": "https://discord.com/api/webhook/082399/lunalytics", + "type": "text", + "error": { + "id": "[id=\"text-input-error-webhook-url\"]", + "value": "Invalid Discord Webhook URL" + } + }, + "username": { + "id": "[id=\"webhook-username\"]", + "value": "Lunalytics", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-webhook-username\"]", + "value": "Invalid Discord Webhook Username" + } + }, + "message": { + "id": "[id=\"text-messsage\"]", + "value": "@everyone Alert!", + "type": "text" + } +} diff --git a/test/e2e/setup/fixtures/notifications/slack.json b/test/e2e/setup/fixtures/notifications/slack.json new file mode 100644 index 0000000..7236258 --- /dev/null +++ b/test/e2e/setup/fixtures/notifications/slack.json @@ -0,0 +1,53 @@ +{ + "type": { + "id": "[id=\"notification-type-dropdown\"]", + "value": "[id=\"notification-type-Slack\"]", + "type": "dropdown" + }, + "friendlyName": { + "id": "[id=\"friendly-name\"]", + "value": "Slack", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-friendly-name\"]", + "value": "Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only." + } + }, + + "url": { + "id": "[id=\"webhook-url\"]", + "value": "https://hooks.slack.com/services/123123/123123/123123", + "invalidValue": "https://hooks.slack.com/service/123123/123123/123123", + "type": "text", + "error": { + "id": "[id=\"text-input-error-webhook-url\"]", + "value": "Invalid Slack Webhook URL" + } + }, + "username": { + "id": "[id=\"webhook-username\"]", + "value": "Lunalytics", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-webhook-username\"]", + "value": "Invalid Slack Webhook Username" + } + }, + "channel": { + "id": "[id=\"channel-name\"]", + "value": "general", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-channel-name\"]", + "value": "Invalid Channel Name" + } + }, + "message": { + "id": "[id=\"text-messsage\"]", + "value": "@everyone Alert!", + "type": "text" + } +} diff --git a/test/e2e/setup/fixtures/notifications/telegram.json b/test/e2e/setup/fixtures/notifications/telegram.json new file mode 100644 index 0000000..4d43132 --- /dev/null +++ b/test/e2e/setup/fixtures/notifications/telegram.json @@ -0,0 +1,37 @@ +{ + "type": { + "id": "[id=\"notification-type-dropdown\"]", + "value": "[id=\"notification-type-Telegram\"]", + "type": "dropdown" + }, + "friendlyName": { + "id": "[id=\"friendly-name\"]", + "value": "Slack", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-friendly-name\"]", + "value": "Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only." + } + }, + "url": { + "id": "[id=\"bot-token\"]", + "value": "123123123123123123", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-bot-token\"]", + "value": "Invalid Telegram Bot Token" + } + }, + "chatId": { + "id": "[id=\"chat-id\"]", + "value": "123123123", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-chat-id\"]", + "value": "Invalid Chat ID" + } + } +} diff --git a/test/e2e/setup/fixtures/notifications/webhooks.json b/test/e2e/setup/fixtures/notifications/webhooks.json new file mode 100644 index 0000000..4b5fad7 --- /dev/null +++ b/test/e2e/setup/fixtures/notifications/webhooks.json @@ -0,0 +1,27 @@ +{ + "type": { + "id": "[id=\"notification-type-dropdown\"]", + "value": "[id=\"notification-type-Webhook\"]", + "type": "dropdown" + }, + "friendlyName": { + "id": "[id=\"friendly-name\"]", + "value": "Webhook", + "invalidValue": "{}[]||<>", + "type": "text", + "error": { + "id": "[id=\"text-input-error-friendly-name\"]", + "value": "Invalid Friendly Name. Must be alphanumeric, dashes, and underscores only." + } + }, + "url": { + "id": "[id=\"webhook-url\"]", + "value": "https://lunalytics.xyz/webhooks/example", + "invalidValue": "this is not a webhook url", + "type": "text", + "error": { + "id": "[id=\"text-input-error-webhook-url\"]", + "value": "Invalid Webhook URL" + } + } +} diff --git a/test/e2e/setup/support/commands.js b/test/e2e/setup/support/commands.js index c40dc0c..7154a7a 100644 --- a/test/e2e/setup/support/commands.js +++ b/test/e2e/setup/support/commands.js @@ -10,6 +10,10 @@ Cypress.Commands.add('typeText', (id, value) => { return cy.get(id).type(value); }); +Cypress.Commands.add('clearText', (id) => { + return cy.get(id).clear(); +}); + Cypress.Commands.add('registerUser', (email, username, password) => { cy.visit('/register'); @@ -33,35 +37,105 @@ Cypress.Commands.add('loginUser', (email, password) => { cy.get('[class="auth-button"]').click(); }); +Cypress.Commands.add('createMonitor', (details = {}) => { + cy.get('[id="home-add-monitor-button"]').click(); + + Object.keys(details).forEach((key) => { + const value = details[key]; + const { id, value: elementValue, error, type, invalidValue } = value; + + if (invalidValue) { + if (type === 'text') { + cy.typeText(id, invalidValue); + } + + cy.get('[id="monitor-create-button"]').click(); + cy.get(error.id).should('be.visible'); + cy.equals(error.id, error.value); + } + + if (type === 'click') { + cy.get(id).click(); + } + + if (elementValue) { + if (type === 'text') { + cy.clearText(id); + cy.typeText(id, elementValue); + } else if (type === 'dropdown') { + cy.get(id).click(); + cy.get(elementValue).click(); + } + } + }); + + cy.get('[id="monitor-create-button"]').click(); +}); + Cypress.Commands.add( - 'createMonitor', - (name, type, url, method, interval, retryInterval, timeout) => { + 'createInvalidMonitor', + (details = {}, showAdvance = false) => { cy.visit('/'); cy.get('[id="home-add-monitor-button"]').click(); - if (name && type) { - cy.typeText(name.id, name.value); - cy.get(type.id).click(); - cy.get(type.value).click(); - - cy.get('[id="Next"]').click(); + if (showAdvance) { + cy.get('[id="monitor-advanced-settings"]').click(); } - if (url && method) { - cy.typeText(url.id, url.value); + Object.keys(details).forEach((key) => { + const value = details[key]; + const { id, type, value: elementValue, error, invalidValue } = value; - cy.get(method.id).click(); - cy.get(method.value).click(); + if (invalidValue && type === 'text') { + cy.typeText(id, invalidValue); + cy.get('[id="monitor-create-button"]').click(); - cy.get('[id="Next"]').click(); + cy.equals(error.id, error.value); + } + + if (type === 'dropdown') { + cy.get(id).click(); + cy.get(elementValue).click(); + } + }); + } +); + +Cypress.Commands.add('createNotification', (details = {}) => { + cy.get('[id="home-add-notification-button"]').click(); + + Object.keys(details).forEach((key) => { + const value = details[key]; + const { id, type, value: elementValue, error, invalidValue } = value; + + if (invalidValue) { + if (type === 'text') { + cy.typeText(id, invalidValue); + } else if (type === 'dropdown') { + cy.get(id).click(); + cy.get(invalidValue).click(); + } + + cy.get('[id="notification-create-button"]').click(); + + cy.get(error.id).should('be.visible'); + cy.equals(error.id, error.value); } - if (interval && retryInterval && timeout) { - cy.typeText(interval.id, interval.value); - cy.typeText(retryInterval.id, retryInterval.value); - cy.typeText(timeout.id, timeout.value); + if (elementValue) { + if (type === 'text') { + cy.clearText(id); + cy.typeText(id, elementValue); + } else if (type === 'dropdown') { + cy.get(id).click(); + cy.get(elementValue).click(); + } + } - cy.get('[id="Submit"]').click(); + if (type === 'checkbox') { + cy.get(id).click(); } - } -); + }); + + cy.get('[id="notification-create-button"]').click(); +}); diff --git a/test/server/middleware/monitor/add.test.js b/test/server/middleware/monitor/add.test.js index 5bf4a85..b2f7a35 100644 --- a/test/server/middleware/monitor/add.test.js +++ b/test/server/middleware/monitor/add.test.js @@ -42,7 +42,7 @@ describe('Add Monitor - Middleware', () => { certificates: { get: vi.fn().mockReturnValue({ isValid: false }), }, - setTimeout: vi.fn(), + checkStatus: vi.fn(), }; userExists = vi.fn().mockReturnValue({ email: 'KSJaay@lunalytics.xyz' }); @@ -66,6 +66,8 @@ describe('Add Monitor - Middleware', () => { interval: 60, email: 'KSJaay@lunalytics.xyz', valid_status_codes: JSON.stringify(['200-299']), + notificationType: 'All', + notificationId: null, method: 'GET', headers: null, body: null, @@ -153,7 +155,7 @@ describe('Add Monitor - Middleware', () => { it('should call setTimeout with monitorId and interval', async () => { await monitorAdd(fakeRequest, fakeResponse); - expect(cache.setTimeout).toHaveBeenCalledWith('test', 60); + expect(cache.checkStatus).toHaveBeenCalledWith('test'); }); it('should return 200 when data is valid', async () => { @@ -173,6 +175,8 @@ describe('Add Monitor - Middleware', () => { interval: 60, email: 'KSJaay@lunalytics.xyz', valid_status_codes: null, + notificationType: 'All', + notificationId: null, method: null, headers: null, body: null, @@ -252,7 +256,7 @@ describe('Add Monitor - Middleware', () => { it('should call setTimeout with monitorId and interval', async () => { await monitorAdd(fakeRequest, fakeResponse); - expect(cache.setTimeout).toHaveBeenCalledWith('test', 60); + expect(cache.checkStatus).toHaveBeenCalledWith('test'); }); it('should return 200 when data is valid', async () => { diff --git a/test/server/middleware/monitor/edit.test.js b/test/server/middleware/monitor/edit.test.js index 42b4875..ed56560 100644 --- a/test/server/middleware/monitor/edit.test.js +++ b/test/server/middleware/monitor/edit.test.js @@ -42,7 +42,7 @@ describe('Edit Monitor - Middleware', () => { certificates: { get: vi.fn().mockReturnValue({ isValid: false }), }, - setTimeout: vi.fn(), + checkStatus: vi.fn(), }; userExists = vi.fn().mockReturnValue({ email: 'KSJaay@lunalytics.xyz' }); @@ -66,6 +66,8 @@ describe('Edit Monitor - Middleware', () => { interval: 60, email: 'KSJaay@lunalytics.xyz', valid_status_codes: JSON.stringify(['200-299']), + notificationType: 'All', + notificationId: null, method: 'GET', headers: null, body: null, @@ -151,10 +153,10 @@ describe('Edit Monitor - Middleware', () => { ); }); - it('should call setTimeout with monitorId and interval', async () => { + it('should call checkStatus with monitorId and interval', async () => { await monitorEdit(fakeRequest, fakeResponse); - expect(cache.setTimeout).toHaveBeenCalledWith('test', 60); + expect(cache.checkStatus).toHaveBeenCalledWith('test'); }); it('should return 200 when data is valid', async () => { @@ -174,6 +176,8 @@ describe('Edit Monitor - Middleware', () => { interval: 60, email: 'KSJaay@lunalytics.xyz', valid_status_codes: null, + notificationType: 'All', + notificationId: null, method: null, headers: null, body: null, @@ -251,10 +255,10 @@ describe('Edit Monitor - Middleware', () => { ); }); - it('should call setTimeout with monitorId and interval', async () => { + it('should call checkStatus with monitorId and interval', async () => { await monitorEdit(fakeRequest, fakeResponse); - expect(cache.setTimeout).toHaveBeenCalledWith('test', 60); + expect(cache.checkStatus).toHaveBeenCalledWith('test'); }); it('should return 200 when data is valid', async () => { diff --git a/test/server/middleware/user/update/password.test.js b/test/server/middleware/user/update/password.test.js index 3cad730..85ca3c7 100644 --- a/test/server/middleware/user/update/password.test.js +++ b/test/server/middleware/user/update/password.test.js @@ -4,10 +4,10 @@ import { userExists, } from '../../../../../server/database/queries/user'; import userUpdatePassword from '../../../../../server/middleware/user/update/password'; -import { verifyPassword } from '../../../../../shared/utils/hashPassword'; +import { verifyPassword } from '../../../../../server/utils/hashPassword'; vi.mock('../../../../../server/database/queries/user'); -vi.mock('../../../../../shared/utils/hashPassword'); +vi.mock('../../../../../server/utils/hashPassword'); describe('userUpdatePassword - Middleware', () => { const user = {