From e2a59a566f0df3409957c7541951ca7266d53f1c Mon Sep 17 00:00:00 2001 From: Simon Priet Date: Fri, 3 Sep 2021 09:29:15 +0200 Subject: [PATCH] refactor: added Mailosaur to manage sms --- cypress.json | 5 +- cypress/integration/lifen-inbox/inbox.spec.js | 18 +- cypress/support/commands.js | 1 + node_modules/cypress-mailosaur/LICENSE | 21 + node_modules/cypress-mailosaur/README.md | 162 +++++++ node_modules/cypress-mailosaur/index.js | 5 + node_modules/cypress-mailosaur/package.json | 118 +++++ .../src/mailosaurCommands.d.ts | 408 ++++++++++++++++++ .../src/mailosaurCommands.js | 210 +++++++++ .../cypress-mailosaur/src/register.js | 15 + node_modules/cypress-mailosaur/src/request.js | 56 +++ package-lock.json | 6 + package.json | 3 +- 13 files changed, 1021 insertions(+), 7 deletions(-) create mode 100644 node_modules/cypress-mailosaur/LICENSE create mode 100644 node_modules/cypress-mailosaur/README.md create mode 100644 node_modules/cypress-mailosaur/index.js create mode 100644 node_modules/cypress-mailosaur/package.json create mode 100644 node_modules/cypress-mailosaur/src/mailosaurCommands.d.ts create mode 100644 node_modules/cypress-mailosaur/src/mailosaurCommands.js create mode 100644 node_modules/cypress-mailosaur/src/register.js create mode 100644 node_modules/cypress-mailosaur/src/request.js diff --git a/cypress.json b/cypress.json index 13ef5f17..52122158 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,6 @@ { - "baseUrl": "https://app.post-prod.lifen.fr" + "baseUrl": "https://app.post-prod.lifen.fr", + "env": { + "MAILOSAUR_API_KEY": "your-key-here" + } } diff --git a/cypress/integration/lifen-inbox/inbox.spec.js b/cypress/integration/lifen-inbox/inbox.spec.js index 519d84af..47dd4fbc 100644 --- a/cypress/integration/lifen-inbox/inbox.spec.js +++ b/cypress/integration/lifen-inbox/inbox.spec.js @@ -1,24 +1,24 @@ /// context('The login page', () => { - describe('Test the login page', () => { + describe.skip('Test the login page', () => { it('Access the webpage', function() { - cy.visit('/'); + cy.visit('/login'); cy.get('#email').should("have.attr", "type", "email"); cy.get('#password').should("have.attr", "type", "password"); //cy.get('#password').type(mdp); }); it('Log an invalid email', function() { - cy.visit('/'); + cy.visit('/login'); cy.get('#email').type("not an email"); cy.get('#password').type("somepasseword {enter}"); cy.url().should('include', 'login?'); }); it('Log an unexisting user', function() { - cy.visit('/'); + cy.visit('/login'); cy.get('#email').type("toto@yopmail.com"); cy.get('#password').type("somepasseword {enter}"); cy.get('#error-message').should("have.class", "alert alert-danger").should("be.visible"); @@ -28,9 +28,17 @@ context('The login page', () => { describe("It send an SMS", function() { it('Log the candidate user', function(){ - cy.visit('/login'); + cy.visit('/'); cy.get('#email').type("candidature-qa@example.org"); cy.get('#password').type("kHwWawhH5ADNuFb"); + cy.get('#continueButton').click(); + + cy.mailosaurGetMessage(serverId, { + sentTo: smsNumber + }).as('sms'); + + cy.get('#challenge_mfa_form_candidate_otp').type('@sms'); + cy.get('#validateButton').click(); }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 119ab03f..2f8764cc 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -23,3 +23,4 @@ // // -- This will overwrite an existing command -- // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +import 'cypress-mailosaur'; \ No newline at end of file diff --git a/node_modules/cypress-mailosaur/LICENSE b/node_modules/cypress-mailosaur/LICENSE new file mode 100644 index 00000000..10a8cb16 --- /dev/null +++ b/node_modules/cypress-mailosaur/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Mailosaur Ltd (https://mailosaur.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/cypress-mailosaur/README.md b/node_modules/cypress-mailosaur/README.md new file mode 100644 index 00000000..f0249e77 --- /dev/null +++ b/node_modules/cypress-mailosaur/README.md @@ -0,0 +1,162 @@ +# Mailosaur Cypress Commands + +[![](https://github.com/mailosaur/cypress-mailosaur/workflows/CI/badge.svg)](https://github.com/mailosaur/cypress-mailosaur/actions) + +## Test email and SMS messages with Cypress + +Using Cypress and Mailosaur together you can: +- Test email, with unlimited test email addresses +- Test SMS messages +- Capture emails with your very own fake SMTP servers + +## What is Mailosaur? + +[Mailosaur](https://mailosaur.com) is a service that lets you automate email testing (e.g. email verification, password resets, etc.) and SMS testing (e.g. one-time passwords). + +Mailosaur also provides dummy SMTP servers to test with; allowing you to catch email in staging environments - preventing email being sent to customers by mistake. + +## How do I test email with Cypress? + +Follow these steps to start testing email with Cypress: + +### Step 1 - Installation + +Install the Mailosaur commands via `npm`: + +```sh +npm install cypress-mailosaur --save-dev +``` + +Once downloaded, add the following line to `cypress/support/index.js` to import the commands into your Cypress project: + +```js +require('cypress-mailosaur'); +``` + +### Step 2 - API Authentication + +Mailosaur commands need your Mailosaur API key to work. You can learn about [managing API keys here](https://mailosaur.com/docs/managing-your-account/api-keys/). + +#### Option 1: Add API key to `cypress.json` + +``` +{ + "env": { + "MAILOSAUR_API_KEY": "your-key-here" + } +} +``` + +#### Option 2: Add API key to a `cypress.env.json` file + +You can create your own `cypress.env.json` file that Cypress will automatically check. This is useful because if you add `cypress.env.json` to your `.gitignore` file, the values in here can be different for each developer machine. + +``` +{ + "MAILOSAUR_API_KEY": "your-key-here" +} +``` + +#### Option 3: Set API key via a system environment variable + +To set the environment variable on your machine, it needs to be prefixed with either `CYPRESS_` or `cypress_`. + +```sh +export CYPRESS_MAILOSAUR_API_KEY=your-key-here +``` + +### Step 3 - Write your email test + +For this example, we'll navigate to a password reset page, request a new password link (sent via email), and get that email. + +Create a new test spec: + +```sh +touch cypress/integration/password-reset.spec.js +``` + +Now edit the file to something like this: + +```js +describe('Password reset', () => { + const serverId = 'abcd1234' + const serverDomain = 'abcd1234.mailosaur.net' + const emailAddress = 'password-reset@' + serverDomain + + it('Makes a Password Reset request', () => { + cy.visit('https://github.com/password_reset') + cy.title().should('equal', 'Forgot your password?') + cy.get('#email_field').type(emailAddress) + }) + + it('Gets Password Reset email from Mailosaur', () => { + cy.mailosaurGetMessage(serverId, { + sentTo: emailAddress + }).then(email => { + expect(email.subject).to.equal('Reset your password'); + passwordResetLink = email.text.links[0].href; + }) + }) + + it('Follows the link from the email', () => { + const validPassword = 'delighted cheese jolly cloud' + + cy.visit(passwordResetLink) + cy.title().should('contain', 'Change your password') + cy.get('#password').type(validPassword) + cy.get('#password_confirmation').type(validPassword) + cy.get('form').submit() + }) +}) +``` + +### Step 4 - Write further test cases + +You can test pretty much anything with Mailosaur and Cypress, including: + +- [Test the text content of an email](https://mailosaur.com/docs/test-cases/text-content/) +- [Test links within an email](https://mailosaur.com/docs/test-cases/links/) +- [Working with email attachments](https://mailosaur.com/docs/test-cases/attachments/) +- [Handling images and web beacons](https://mailosaur.com/docs/test-cases/images/) +- [Perform a spam test](https://mailosaur.com/docs/test-cases/spam/) + +For more information, check out the full [Mailosaur docs](https://mailosaur.com/docs/frameworks-and-tools/cypress/) for the most up-to-date guides and troubleshooting tips. + +## How do I test SMS with Cypress? + +Mailosaur Team, Premium, and Ultimate customers can perform SMS tests with Cypress, whilst Trial account users can just ask support to enable this feature to try it out! + +SMS testing works in just the same way as email testing above. However rather than dealing with email addresses, you search using phone numbers instead. For example: + +```js +cy.mailosaurGetMessage(serverId, { + sentTo: '447555111222' +}).then(sms => { + expect(sms.text.body).to.equal('Your OTP code is: 123456') +}) +``` + +## Development + +Install all development dependencies: + +```sh +npm i +``` + +The test suite requires the following environment variables to be set: + +```sh +export CYPRESS_MAILOSAUR_API_KEY=your_api_key +export CYPRESS_MAILOSAUR_SERVER=server_id +``` + +Run all tests: + +```sh +npm test +``` + +## Contacting us + +You can get us at [support@mailosaur.com](mailto:support@mailosaur.com) diff --git a/node_modules/cypress-mailosaur/index.js b/node_modules/cypress-mailosaur/index.js new file mode 100644 index 00000000..94f25f4e --- /dev/null +++ b/node_modules/cypress-mailosaur/index.js @@ -0,0 +1,5 @@ +/* global Cypress */ + +const { register } = require("./src/register"); + +register(Cypress); diff --git a/node_modules/cypress-mailosaur/package.json b/node_modules/cypress-mailosaur/package.json new file mode 100644 index 00000000..5f3ba7a9 --- /dev/null +++ b/node_modules/cypress-mailosaur/package.json @@ -0,0 +1,118 @@ +{ + "_from": "cypress-mailosaur", + "_id": "cypress-mailosaur@2.3.3", + "_inBundle": false, + "_integrity": "sha512-Xo3NqXm0kxdozwAdpCgv2bRLgZyMuGaQS7CK7+e8oFQWFaG/e+zamg8KSbi/n2ROSjm5CXh0WFNMHgBYZ8wZjg==", + "_location": "/cypress-mailosaur", + "_phantomChildren": {}, + "_requested": { + "type": "tag", + "registry": true, + "raw": "cypress-mailosaur", + "name": "cypress-mailosaur", + "escapedName": "cypress-mailosaur", + "rawSpec": "", + "saveSpec": null, + "fetchSpec": "latest" + }, + "_requiredBy": [ + "#DEV:/", + "#USER" + ], + "_resolved": "https://registry.npmjs.org/cypress-mailosaur/-/cypress-mailosaur-2.3.3.tgz", + "_shasum": "f7d40905e29f042416e3f504e449b8a320405018", + "_spec": "cypress-mailosaur", + "_where": "/home/simon/Documents/lifen-autotest", + "author": { + "name": "Mailosaur Ltd" + }, + "bugs": { + "url": "https://github.com/mailosaur/mailosaur-node/issues" + }, + "bundleDependencies": false, + "deprecated": false, + "description": "Extends Cypress' cy commands that let you integrate email and SMS testing into your continuous integration process.", + "devDependencies": { + "@types/node": "^15.0.3", + "babel-eslint": "10.1.0", + "coveralls": "3.0.9", + "eslint": "6.8.0", + "eslint-config-airbnb-base": "^14.1.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-react": "^7.20.0", + "jest": "25.4.0", + "sinon": "9.0.2", + "typescript": "^4.2.4" + }, + "engines": { + "node": ">= v10.0.0" + }, + "eslintConfig": { + "extends": [ + "airbnb-base" + ], + "env": { + "node": true, + "mocha": true + }, + "globals": { + "Cypress": true, + "cy": true, + "assert": true + }, + "rules": { + "quotes": [ + 2, + "single", + { + "allowTemplateLiterals": true + } + ], + "consistent-return": 0, + "comma-dangle": 0, + "max-len": 0, + "no-shadow": 0, + "class-methods-use-this": 0, + "linebreak-style": 0, + "arrow-parens": 0, + "operator-linebreak": 0, + "object-curly-newline": 0, + "implicit-arrow-linebreak": 0, + "no-else-return": 0, + "indent": 0, + "lines-between-class-members": 0 + } + }, + "files": [ + "src" + ], + "homepage": "https://github.com/mailosaur/cypress-mailosaur#readme", + "keywords": [ + "cypress", + "mailosaur", + "email", + "sms", + "testing", + "automation", + "testing-tools" + ], + "license": "MIT", + "main": "index.js", + "name": "cypress-mailosaur", + "peerDependencies": { + "cypress": ">= 2.1.0" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/mailosaur/cypress-mailosaur.git" + }, + "scripts": { + "coveralls": "cat ./coverage/lcov.info | coveralls", + "lint": "eslint src test/react-app/src test/react-app/cypress", + "test": "npm run tsc && cd test/react-app && npm run test:ci", + "test:ci": "cd test/react-app && npm i && npm run test:ci", + "tsc": "tsc src/mailosaurCommands.d.ts --types node" + }, + "types": "src/mailosaurCommands.d.ts", + "version": "2.3.3" +} diff --git a/node_modules/cypress-mailosaur/src/mailosaurCommands.d.ts b/node_modules/cypress-mailosaur/src/mailosaurCommands.d.ts new file mode 100644 index 00000000..d8be8625 --- /dev/null +++ b/node_modules/cypress-mailosaur/src/mailosaurCommands.d.ts @@ -0,0 +1,408 @@ +/// + +/** + * @class + * Initializes a new instance of the SpamAssassinRule class. + * @constructor + * @member {number} [score] + * @member {string} [rule] + * @member {string} [description] + */ +export interface SpamAssassinRule { + score?: number; + rule?: string; + description?: string; +} + +/** + * @class + * Initializes a new instance of the SpamFilterResults class. + * @constructor + * @member {array} [spamAssassin] + */ +export interface SpamFilterResults { + spamAssassin?: SpamAssassinRule[]; +} + +/** + * @class + * Initializes a new instance of the SpamAnalysisResult class. + * @constructor + * @member {object} [spamFilterResults] + * @member {array} [spamFilterResults.spamAssassin] + * @member {number} [score] + */ +export interface SpamAnalysisResult { + spamFilterResults?: SpamFilterResults; + score?: number; +} + +/** + * @class + * Initializes a new instance of the MessageAddress class. + * @constructor + * @member {string} [name] Display name, if one is specified. + * @member {string} [email] Email address (applicable to email messages). + * @member {string} [phone] Phone number (applicable to SMS messages). + */ +export interface MessageAddress { + name?: string; + email?: string; + phone?: string; +} + +/** + * @class + * Initializes a new instance of the Link class. + * @constructor + * @member {string} [href] + * @member {string} [text] + */ +export interface Link { + href?: string; + text?: string; +} + +/** + * @class + * Initializes a new instance of the Image class. + * @constructor + * @member {string} [src] + * @member {string} [alt] + */ +export interface Image { + src?: string; + alt?: string; +} + +/** + * @class + * Initializes a new instance of the MessageContent class. + * @constructor + * @member {array} [links] + * @member {array} [images] + * @member {string} [body] + */ +export interface MessageContent { + links?: Link[]; + images?: Image[]; + body?: string; +} + +/** + * @class + * Initializes a new instance of the Attachment class. + * @constructor + * @member {uuid} id + * @member {string} [contentType] + * @member {string} [fileName] + * @member {string} [contentId] + * @member {number} [length] + * @member {string} [url] + */ +export interface Attachment { + id: string; + contentType?: string; + fileName?: string; + contentId?: string; + length?: number; + url?: string; +} + +/** + * @class + * Initializes a new instance of the MessageHeader class. + * @constructor + * @member {string} [field] Header key. + * @member {string} [value] Header value. + */ +export interface MessageHeader { + field?: string; + value?: string; +} + +/** + * @class + * Initializes a new instance of the Metadata class. + * @constructor + * Advanced use case content related to the message. + * + * @member {array} [headers] Email headers. + */ +export interface Metadata { + headers?: MessageHeader[]; +} + +/** + * @class + * Initializes a new instance of the Message class. + * @constructor + * @member {uuid} [id] Unique identifier for the message. + * @member {array} [from] The sender of the message. + * @member {array} [to] The message’s recipient. + * @member {array} [cc] Carbon-copied recipients for email messages. + * @member {array} [bcc] Blind carbon-copied recipients for email messages. + * @member {date} [received] The datetime that this message was received by + * Mailosaur. + * @member {string} [subject] The message’s subject. + * @member {object} [html] Message content that was sent in HTML format. + * @member {array} [html.links] + * @member {array} [html.images] + * @member {string} [html.body] + * @member {object} [text] Message content that was sent in plain text format. + * @member {array} [text.links] + * @member {array} [text.images] + * @member {string} [text.body] + * @member {array} [attachments] An array of attachment metadata for any + * attached files. + * @member {object} [metadata] + * @member {array} [metadata.headers] Email headers. + * @member {string} [server] Identifier for the server in which the message is + * located. + */ +export interface Message { + id?: string; + from?: MessageAddress[]; + to?: MessageAddress[]; + cc?: MessageAddress[]; + bcc?: MessageAddress[]; + received?: Date; + subject?: string; + html?: MessageContent; + text?: MessageContent; + attachments?: Attachment[]; + metadata?: Metadata; + server?: string; +} + +/** + * @class + * Initializes a new instance of the MessageSummary class. + * @constructor + * @member {uuid} id + * @member {string} [server] + * @member {array} [rcpt] + * @member {array} [from] + * @member {array} [to] + * @member {array} [cc] + * @member {array} [bcc] + * @member {date} [received] + * @member {string} [subject] + * @member {string} [summary] + * @member {number} [attachments] + */ +export interface MessageSummary { + id: string; + server?: string; + rcpt?: MessageAddress[]; + from?: MessageAddress[]; + to?: MessageAddress[]; + cc?: MessageAddress[]; + bcc?: MessageAddress[]; + received?: Date; + subject?: string; + summary?: string; + attachments?: number; +} + +/** + * @class + * Initializes a new instance of the MessageListResult class. + * @constructor + * The result of a message listing request. + * + * @member {array} [items] The individual summaries of each message forming the + * result. Summaries are returned sorted by received date, with the most + * recently-received messages appearing first. + */ +export interface MessageListResult { + items?: MessageSummary[]; +} + +/** + * @class + * Initializes a new instance of the SearchCriteria class. + * @constructor + * @member {string} [sentFrom] The full email address from which the target email + * was sent. + * @member {string} [sentTo] The full email address to which the target email + * was sent. + * @member {string} [subject] The value to seek within the target email's + * subject line. + * @member {string} [body] The value to seek within the target email's HTML or + * text body. + * @member {string} [match] If set to ALL (default), then only results that match all + * specified criteria will be returned. If set to ANY, results that match any of the + * specified criteria will be returned. + */ +export interface SearchCriteria { + sentFrom?: string; + sentTo?: string; + subject?: string; + body?: string; + match?: "ALL" | "ANY"; +} + +/** + * @class + * Initializes a new instance of the Server class. + * @constructor + * @member {string} [id] Unique identifier for the server. Used as username for + * SMTP/POP3 authentication. + * @member {string} [name] A name used to identify the server. + * @member {array} [users] Users (excluding administrators) who have access to + * the server. + * @member {number} [messages] The number of messages currently in the server. + */ +export interface Server { + id?: string; + name?: string; + users?: string[]; + messages?: number; +} + +/** + * @class + * Initializes a new instance of the ServerListResult class. + * @constructor + * The result of a server listing request. + * + * @member {array} [items] The individual servers forming the result. Servers + * are returned sorted by creation date, with the most recently-created server + * appearing first. + */ +export interface ServerListResult { + items?: Server[]; +} + +/** + * @class + * Initializes a new instance of the ServerCreateOptions class. + * @constructor + * @member {string} [name] A name used to identify the server. + */ +export interface ServerCreateOptions { + name?: string; +} + +export interface SearchOptions { + timeout?: number, + receivedAfter?: Date, + page?: number, + itemsPerPage?: number, + suppressError?: boolean +} + +declare global { + namespace Cypress { + interface Chainable { + /** + * @summary List all servers + * + * Returns a list of your virtual SMTP servers. Servers are returned sorted in + * alphabetical order. + * + */ + mailosaurListServers( + ): Cypress.Chainable; + + mailosaurCreateServer( + options: ServerCreateOptions + ): Cypress.Chainable; + + mailosaurGetServer( + serverId: string + ): Cypress.Chainable; + + mailosaurGetServerPassword( + serverId: string + ): Cypress.Chainable; + + mailosaurUpdateServer( + server: Server + ): Cypress.Chainable; + + mailosaurDeleteServer( + serverId: string + ): Cypress.Chainable; + + mailosaurDeleteAllMessages( + serverId: string + ): Cypress.Chainable; + + mailosaurListMessages( + serverId: string + ): Cypress.Chainable; + + mailosaurCreateMessage( + serverId: string + ): Cypress.Chainable; + + mailosaurGetMessage( + serverId: string, + criteria: SearchCriteria, + options?: SearchOptions + ): Cypress.Chainable; + + mailosaurGetMessageById( + messageId: string + ): Cypress.Chainable; + + mailosaurSearchMessages( + serverId: string, + criteria: SearchCriteria, + options?: SearchOptions + ): Cypress.Chainable; + + mailosaurGetMessagesBySubject( + serverId: string, + subject: string + ): Cypress.Chainable; + + mailosaurGetMessagesByBody( + serverId: string, + body: string + ): Cypress.Chainable; + + mailosaurGetMessagesBySentFrom( + serverId: string, + sentFrom: string + ): Cypress.Chainable; + + mailosaurGetMessagesBySentTo( + serverId: string, + sentTo: string + ): Cypress.Chainable; + + mailosaurDownloadAttachment( + attachmentId: string + ): Cypress.Chainable; + + mailosaurDownloadMessage( + messageId: string + ): Cypress.Chainable; + + mailosaurDeleteMessage( + messageId: string + ): Cypress.Chainable; + + /** + * @summary Perform a spam test + * + * Perform spam testing on the specified email + * + * @param {string} messageId The identifier of the email to be analyzed. + * + * @returns {Chainable} + */ + mailosaurGetSpamAnalysis( + messageId: string + ): Chainable; + + mailosaurGenerateEmailAddress( + serverId: string + ): Cypress.Chainable; + } + + } +} diff --git a/node_modules/cypress-mailosaur/src/mailosaurCommands.js b/node_modules/cypress-mailosaur/src/mailosaurCommands.js new file mode 100644 index 00000000..bde63306 --- /dev/null +++ b/node_modules/cypress-mailosaur/src/mailosaurCommands.js @@ -0,0 +1,210 @@ +const Request = require('./request'); + +class MailosaurCommands { + static get cypressCommands() { + return [ + 'mailosaurSetApiKey', + 'mailosaurListServers', + 'mailosaurCreateServer', + 'mailosaurGetServer', + 'mailosaurGetServerPassword', + 'mailosaurUpdateServer', + 'mailosaurDeleteServer', + 'mailosaurListMessages', + 'mailosaurCreateMessage', + 'mailosaurGetMessage', + 'mailosaurGetMessageById', + 'mailosaurSearchMessages', + 'mailosaurGetMessagesBySubject', + 'mailosaurGetMessagesByBody', + 'mailosaurGetMessagesBySentFrom', + 'mailosaurGetMessagesBySentTo', + 'mailosaurDeleteMessage', + 'mailosaurDeleteAllMessages', + 'mailosaurDownloadAttachment', + 'mailosaurDownloadMessage', + 'mailosaurGetSpamAnalysis', + 'mailosaurGenerateEmailAddress' + ]; + } + + constructor() { + const defaultApiKey = Cypress.env('MAILOSAUR_API_KEY'); + this.mailosaurSetApiKey(defaultApiKey); + } + + mailosaurSetApiKey(apiKey) { + this.request = new Request({ apiKey, baseUrl: Cypress.env('MAILOSAUR_BASE_URL') }); + } + + mailosaurListServers() { + return this.request.get(`api/servers`); + } + + mailosaurCreateServer(params) { + return this.request.post(`api/servers`, params); + } + + mailosaurGetServer(serverId) { + return this.request.get(`api/servers/${serverId}`); + } + + mailosaurGetServerPassword(serverId) { + return this.request.get(`api/servers/${serverId}/password`) + .then((result) => (result.value)); + } + + mailosaurUpdateServer(server) { + return this.request.put(`api/servers/${server.id}`, server); + } + + mailosaurDeleteServer(serverId) { + return this.request.del(`api/servers/${serverId}`); + } + + mailosaurDeleteAllMessages(serverId) { + return this.request.del(`api/messages?server=${serverId}`); + } + + mailosaurListMessages(serverId) { + return this.request.get(`api/messages?server=${serverId}`); + } + + mailosaurCreateMessage(serverId) { + return this.request.post(`api/messages?server=${serverId}`, {}); + } + + mailosaurGetMessage(server, criteria, options = {}) { + // Only return 1 result + options.page = 0; + options.itemsPerPage = 1; + + // Default timeout to 10s + options.timeout = options.timeout || 10000; // eslint-disable-line no-param-reassign + + // Default receivedAfter to 1h + options.receivedAfter = options.receivedAfter || new Date(Date.now() - 3600000); // eslint-disable-line no-param-reassign + + return cy.mailosaurSearchMessages(server, criteria, options) + .then((result) => ( + cy.mailosaurGetMessageById(result.items[0].id) + )); + } + + mailosaurGetMessageById(messageId) { + return this.request.get(`api/messages/${messageId}`); + } + + mailosaurSearchMessages(serverId, searchCriteria, options = {}) { + let pollCount = 0; + const startTime = Date.now(); + + const qs = { + server: serverId, + page: options.page, + itemsPerPage: options.itemsPerPage, + receivedAfter: options.receivedAfter + }; + + if (!Number.isInteger(options.timeout)) { + options.timeout = 0; // eslint-disable-line no-param-reassign + } + + if (typeof options.errorOnTimeout !== 'boolean') { + options.errorOnTimeout = true; // eslint-disable-line no-param-reassign + } + + const fn = (resolve, reject) => () => { + const reqOptions = this.request.buildOptions('POST', `api/messages/search`); + reqOptions.qs = qs; + reqOptions.json = searchCriteria; + + return Cypress.backend('http:request', reqOptions) + .timeout(10000) + .then((result) => { + const { body, status, headers } = result; + + switch (status) { + case 200: + break; + case 400: + return reject(new Error(JSON.stringify(result.body))); + case 401: + return reject(new Error('Cannot authenticate with Mailosaur (401). Please check your API key.')); + default: + return reject(new Error(`Status: ${status}, Result: ${JSON.stringify(result)}`)); + } + + if (options.timeout && !body.items.length) { + const delayPattern = (headers['x-ms-delay'] || '1000') + .split(',') + .map(x => parseInt(x, 10)); + + const delay = (pollCount >= delayPattern.length) ? + delayPattern[delayPattern.length - 1] : + delayPattern[pollCount]; + + pollCount += 1; + + // Stop if timeout will be exceeded + if (((Date.now() - startTime) + delay) > options.timeout) { + return (options.errorOnTimeout === false) ? + resolve(body) : + reject(new Error('No matching messages found in time. By default, only messages received in the last hour are checked (use receivedAfter to override this).')); + } + + return setTimeout(fn(resolve, reject), delay); + } + + resolve(body); + }); + }; + + cy.wrap(new Cypress.Promise((resolve, reject) => { + fn(resolve, reject)(); + }), { + log: false, + timeout: options.timeout + 10000 + }); + } + + mailosaurGetMessagesBySubject(serverId, subject) { + return cy.mailosaurSearchMessages(serverId, { subject }); + } + + mailosaurGetMessagesByBody(serverId, body) { + return cy.mailosaurSearchMessages(serverId, { body }); + } + + mailosaurGetMessagesBySentFrom(serverId, sentFrom) { + return cy.mailosaurSearchMessages(serverId, { sentFrom }); + } + + mailosaurGetMessagesBySentTo(serverId, sentTo) { + return cy.mailosaurSearchMessages(serverId, { sentTo }); + } + + mailosaurDownloadAttachment(attachmentId) { + return this.request.get(`api/files/attachments/${attachmentId}`); + } + + mailosaurDownloadMessage(messageId) { + return this.request.get(`api/files/email/${messageId}`); + } + + mailosaurDeleteMessage(messageId) { + return this.request.del(`api/messages/${messageId}`); + } + + mailosaurGetSpamAnalysis(messageId) { + return this.request.get(`api/analysis/spam/${messageId}`); + } + + mailosaurGenerateEmailAddress(serverId) { + const host = Cypress.env('MAILOSAUR_SMTP_HOST') || 'mailosaur.net'; + const random = (Math.random() + 1).toString(36).substring(7); + return cy.wrap(`${random}@${serverId}.${host}`); + } +} + +module.exports = MailosaurCommands; diff --git a/node_modules/cypress-mailosaur/src/register.js b/node_modules/cypress-mailosaur/src/register.js new file mode 100644 index 00000000..f7e45fcc --- /dev/null +++ b/node_modules/cypress-mailosaur/src/register.js @@ -0,0 +1,15 @@ +const MailosaurCommands = require('./mailosaurCommands'); + +const register = (Cypress) => { + const mailosaurCommands = new MailosaurCommands(); + MailosaurCommands.cypressCommands.forEach((commandName) => { + Cypress.Commands.add( + commandName, + mailosaurCommands[commandName].bind(mailosaurCommands) + ); + }); +}; + +module.exports = { + register, +}; diff --git a/node_modules/cypress-mailosaur/src/request.js b/node_modules/cypress-mailosaur/src/request.js new file mode 100644 index 00000000..bc7e8733 --- /dev/null +++ b/node_modules/cypress-mailosaur/src/request.js @@ -0,0 +1,56 @@ +const pkg = require('../package.json'); + +/* eslint-disable max-classes-per-file */ +class Request { + constructor(options) { + this.baseUrl = options.baseUrl || 'https://mailosaur.com/'; + this.apiKey = options.apiKey; + const encodedKey = Buffer.from(`${this.apiKey}:`).toString('base64'); + this.headers = { + Accept: 'application/json', + Authorization: `Basic ${encodedKey}`, + 'User-Agent': `cypress-mailosaur/${pkg.version}` + }; + } + + buildOptions(method, path) { + if (!this.apiKey) { + // CYPRESS_ prefix necessary per https://docs.cypress.io/guides/guides/environment-variables.html#Option-3-CYPRESS + throw new Error('You must set the CYPRESS_MAILOSAUR_API_KEY environment variable to use the Mailosaur plugin.'); + } + + return { + method, + url: `${this.baseUrl}${path}`, + headers: { + Accept: this.headers.Accept, + Authorization: this.headers.Authorization, + 'User-Agent': this.headers['User-Agent'] + } + }; + } + + request(method, path, body) { + const options = this.buildOptions(method, path); + options.body = body || undefined; + return cy.request(options).its('body'); + } + + get(path) { + return this.request('GET', path); + } + + post(path, body) { + return this.request('POST', path, body); + } + + put(path, body) { + return this.request('PUT', path, body); + } + + del(path) { + return this.request('DELETE', path); + } +} + +module.exports = Request; diff --git a/package-lock.json b/package-lock.json index 937609dc..9a813fe6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2514,6 +2514,12 @@ } } }, + "cypress-mailosaur": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/cypress-mailosaur/-/cypress-mailosaur-2.3.3.tgz", + "integrity": "sha512-Xo3NqXm0kxdozwAdpCgv2bRLgZyMuGaQS7CK7+e8oFQWFaG/e+zamg8KSbi/n2ROSjm5CXh0WFNMHgBYZ8wZjg==", + "dev": true + }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", diff --git a/package.json b/package.json index 62218bf8..78e7c58c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "cypress": "^8.3.1" }, "devDependencies": { - "cypress-cucumber-preprocessor": "^4.2.0" + "cypress-cucumber-preprocessor": "^4.2.0", + "cypress-mailosaur": "^2.3.3" }, "scripts": { "test": "cypress open"