diff --git a/.github/workflows/blank.yml b/.github/workflows/blank.yml index 3b7483070..95caebbc5 100644 --- a/.github/workflows/blank.yml +++ b/.github/workflows/blank.yml @@ -42,7 +42,7 @@ jobs: node-version: 12.x - run: npm ci - run: npm run lint - test: + unittest: runs-on: ubuntu-latest name: "Unit Tests" steps: @@ -75,8 +75,8 @@ jobs: with: name: dist path: dist/ - endtoend: - name: "End to End Tests" + endtoendemulator: + name: "End To End Tests | Emulator | SQL" runs-on: windows-latest steps: - uses: actions/checkout@v2 @@ -101,9 +101,65 @@ jobs: EMULATOR_ENDPOINT: https://0.0.0.0:8081/ NODE_TLS_REJECT_UNAUTHORIZED: 0 CYPRESS_CACHE_FOLDER: ~/.cache/Cypress + endtoendsql: + name: "End To End Tests | SQL" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + - name: Restore Cypress Binary Cache + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: ${{ runner.os }}-cypress-binary-cache + - run: npm ci + - name: End to End Tests + run: | + npm start & + cd cypress + npm ci + node cleanup.js + npm run wait-for-server + npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/SQL/*" + shell: bash + env: + NODE_TLS_REJECT_UNAUTHORIZED: 0 + CYPRESS_CACHE_FOLDER: ~/.cache/Cypress + CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }} + endtoendmongo: + name: "End To End Tests | Mongo" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + - name: Restore Cypress Binary Cache + uses: actions/cache@v2 + with: + path: ~/.cache/Cypress + key: ${{ runner.os }}-cypress-binary-cache + - name: End to End Tests + run: | + npm ci + npm start & + cd cypress + npm ci + node cleanup.js + npm run wait-for-server + npx cypress run --browser chrome --headless --spec "./integration/dataexplorer/MONGO/*" + shell: bash + env: + NODE_TLS_REJECT_UNAUTHORIZED: 0 + CYPRESS_CACHE_FOLDER: ~/.cache/Cypress + CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} nuget: name: Publish Nuget - needs: [build, test, endtoend] + needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] runs-on: ubuntu-latest env: NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} @@ -124,7 +180,7 @@ jobs: path: "*.nupkg" nugetmpac: name: Publish Nuget MPAC - needs: [build, test, endtoend] + needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] runs-on: ubuntu-latest env: NUGET_SOURCE: ${{ secrets.NUGET_SOURCE }} diff --git a/cypress/.gitignore b/cypress/.gitignore index d5a44f201..741e4121e 100644 --- a/cypress/.gitignore +++ b/cypress/.gitignore @@ -1 +1,4 @@ -cypress.env.json \ No newline at end of file +cypress.env.json +cypress/report +cypress/screenshots +cypress/videos \ No newline at end of file diff --git a/cypress/cleanup.js b/cypress/cleanup.js new file mode 100644 index 000000000..771529aa7 --- /dev/null +++ b/cypress/cleanup.js @@ -0,0 +1,51 @@ +// Cleans up old databases from previous test runs +const { CosmosClient } = require("@azure/cosmos"); + +// TODO: Add support for other API connection strings +const mongoRegex = RegExp("mongodb://.*:(.*)@(.*).mongo.cosmos.azure.com"); + +async function cleanup() { + const connectionString = process.env.CYPRESS_CONNECTION_STRING; + if (!connectionString) { + throw new Error("Connection string not provided"); + } + + let client; + switch (true) { + case connectionString.includes("mongodb://"): { + const [, key, accountName] = connectionString.match(mongoRegex); + client = new CosmosClient({ + key, + endpoint: `https://${accountName}.documents.azure.com:443/` + }); + break; + } + // TODO: Add support for other API connection strings + default: + client = new CosmosClient(connectionString); + break; + } + + const response = await client.databases.readAll().fetchAll(); + return Promise.all( + response.resources.map(async db => { + const dbTimestamp = new Date(db._ts * 1000); + const twentyMinutesAgo = new Date(Date.now() - 1000 * 60 * 20); + if (dbTimestamp < twentyMinutesAgo) { + await client.database(db.id).delete(); + console.log(`DELETED: ${db.id} | Timestamp: ${dbTimestamp}`); + } else { + console.log(`SKIPPED: ${db.id} | Timestamp: ${dbTimestamp}`); + } + }) + ); +} + +cleanup() + .then(() => { + process.exit(0); + }) + .catch(error => { + console.error(error); + process.exit(1); + }); diff --git a/cypress/integration/dataexplorer/MONGO/addCollection.spec.ts b/cypress/integration/dataexplorer/MONGO/addCollection.spec.ts index 56d2fa868..ea06e5472 100644 --- a/cypress/integration/dataexplorer/MONGO/addCollection.spec.ts +++ b/cypress/integration/dataexplorer/MONGO/addCollection.spec.ts @@ -16,7 +16,7 @@ let crypt = require("crypto"); context("Mongo API Test - createDatabase", () => { beforeEach(() => { - connectionString.loginUsingConnectionString(connectionString.constants.mongo); + connectionString.loginUsingConnectionString(); }); it("Create a new collection in Mongo API", () => { @@ -63,7 +63,7 @@ context("Mongo API Test - createDatabase", () => { .type(sharedKey); cy.wrap($body) - .find('input[data-test="addCollection-createCollection"]') + .find("#submitBtnAddCollection") .click(); cy.wait(10000); diff --git a/cypress/integration/dataexplorer/MONGO/addCollectionAutopilot.spec.ts b/cypress/integration/dataexplorer/MONGO/addCollectionAutopilot.spec.ts index af9f3fe61..d9b064578 100644 --- a/cypress/integration/dataexplorer/MONGO/addCollectionAutopilot.spec.ts +++ b/cypress/integration/dataexplorer/MONGO/addCollectionAutopilot.spec.ts @@ -16,10 +16,10 @@ let crypt = require("crypto"); context("Mongo API Test", () => { beforeEach(() => { - connectionString.loginUsingConnectionString(connectionString.constants.mongo); + connectionString.loginUsingConnectionString(); }); - it("Create a new collection in Mongo API - Autopilot", () => { + it.skip("Create a new collection in Mongo API - Autopilot", () => { const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; diff --git a/cypress/integration/dataexplorer/MONGO/addCollectionExistingDatabase.spec.ts b/cypress/integration/dataexplorer/MONGO/addCollectionExistingDatabase.spec.ts index c012ef8b9..8661cbdce 100644 --- a/cypress/integration/dataexplorer/MONGO/addCollectionExistingDatabase.spec.ts +++ b/cypress/integration/dataexplorer/MONGO/addCollectionExistingDatabase.spec.ts @@ -4,10 +4,10 @@ let crypt = require("crypto"); context("Mongo API Test", () => { beforeEach(() => { - connectionString.loginUsingConnectionString(connectionString.constants.mongo); + connectionString.loginUsingConnectionString(); }); - it("Create a new collection in existing database in Mongo API", () => { + it.skip("Create a new collection in existing database in Mongo API", () => { const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; diff --git a/cypress/integration/dataexplorer/MONGO/provisionDatabaseThroughput.spec.ts b/cypress/integration/dataexplorer/MONGO/provisionDatabaseThroughput.spec.ts index b6c40f183..d6bc4d0e3 100644 --- a/cypress/integration/dataexplorer/MONGO/provisionDatabaseThroughput.spec.ts +++ b/cypress/integration/dataexplorer/MONGO/provisionDatabaseThroughput.spec.ts @@ -2,9 +2,9 @@ const connectionString = require("../../../utilities/connectionString"); let crypt = require("crypto"); -context("Mongo API Test", () => { +context.skip("Mongo API Test", () => { beforeEach(() => { - connectionString.loginUsingConnectionString(connectionString.constants.mongo); + connectionString.loginUsingConnectionString(); }); it("Create a new collection in Mongo API - Provision database throughput", () => { diff --git a/cypress/integration/dataexplorer/SQL/addCollection.spec.ts b/cypress/integration/dataexplorer/SQL/addCollection.spec.ts index 6ac9965a7..a3259a80e 100644 --- a/cypress/integration/dataexplorer/SQL/addCollection.spec.ts +++ b/cypress/integration/dataexplorer/SQL/addCollection.spec.ts @@ -16,13 +16,14 @@ let crypt = require("crypto"); context("SQL API Test", () => { beforeEach(() => { - connectionString.loginUsingConnectionString(connectionString.constants.sql); + connectionString.loginUsingConnectionString(); }); it("Create a new container in SQL API", () => { const dbId = `TestDatabase${crypt.randomBytes(8).toString("hex")}`; const collectionId = `TestCollection${crypt.randomBytes(8).toString("hex")}`; const sharedKey = `SharedKey${crypt.randomBytes(8).toString("hex")}`; + connectionString.loginUsingConnectionString(); cy.get("iframe").then($element => { const $body = $element.contents().find("body"); @@ -63,7 +64,7 @@ context("SQL API Test", () => { .type(sharedKey); cy.wrap($body) - .find('input[data-test="addCollection-createCollection"]') + .find("#submitBtnAddCollection") .click(); cy.wait(10000); diff --git a/cypress/package-lock.json b/cypress/package-lock.json index 5398ee8d5..8f8a5a665 100644 --- a/cypress/package-lock.json +++ b/cypress/package-lock.json @@ -273,77 +273,12 @@ "any-observable": "^0.3.0" } }, - "@types/blob-util": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@types/blob-util/-/blob-util-1.3.3.tgz", - "integrity": "sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w==", + "@types/sinonjs__fake-timers": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz", + "integrity": "sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==", "dev": true }, - "@types/bluebird": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz", - "integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw==", - "dev": true - }, - "@types/chai": { - "version": "4.2.7", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.7.tgz", - "integrity": "sha512-luq8meHGYwvky0O7u0eQZdA7B4Wd9owUCqvbw2m3XCrCU8mplYOujMBbvyS547AxJkC+pGnd0Cm15eNxEUNU8g==", - "dev": true - }, - "@types/chai-jquery": { - "version": "1.1.40", - "resolved": "https://registry.npmjs.org/@types/chai-jquery/-/chai-jquery-1.1.40.tgz", - "integrity": "sha512-mCNEZ3GKP7T7kftKeIs7QmfZZQM7hslGSpYzKbOlR2a2HCFf9ph4nlMRA9UnuOETeOQYJVhJQK7MwGqNZVyUtQ==", - "dev": true, - "requires": { - "@types/chai": "*", - "@types/jquery": "*" - } - }, - "@types/jquery": { - "version": "3.3.31", - "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.31.tgz", - "integrity": "sha512-Lz4BAJihoFw5nRzKvg4nawXPzutkv7wmfQ5121avptaSIXlDNJCUuxZxX/G+9EVidZGuO0UBlk+YjKbwRKJigg==", - "dev": true, - "requires": { - "@types/sizzle": "*" - } - }, - "@types/lodash": { - "version": "4.14.149", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.149.tgz", - "integrity": "sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==", - "dev": true - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/sinon": { - "version": "7.5.1", - "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-7.5.1.tgz", - "integrity": "sha512-EZQUP3hSZQyTQRfiLqelC9NMWd1kqLcmQE0dMiklxBkgi84T+cHOhnKpgk4NnOWpGX863yE6+IaGnOXUNFqDnQ==", - "dev": true - }, - "@types/sinon-chai": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.3.tgz", - "integrity": "sha512-TOUFS6vqS0PVL1I8NGVSNcFaNJtFoyZPXZ5zur+qlhDfOmQECZZM4H4kKgca6O8L+QceX/ymODZASfUfn+y4yQ==", - "dev": true, - "requires": { - "@types/chai": "*", - "@types/sinon": "*" - } - }, "@types/sizzle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz", @@ -827,24 +762,15 @@ } }, "cypress": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.5.0.tgz", - "integrity": "sha512-2A4g5FW5d2fHzq8HKUGAMVTnW6P8nlWYQALiCoGN4bqBLvgwhYM/oG9oKc2CS6LnvgHFiKivKzpm9sfk3uU3zQ==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-4.8.0.tgz", + "integrity": "sha512-Lsff8lF8pq6k/ioNua783tCsxGSLp6gqGXecdIfqCkqjYiOA53XKuEf1CaQJLUVs1dHSf49eDUp/sb620oJjVQ==", "dev": true, "requires": { "@cypress/listr-verbose-renderer": "0.4.1", "@cypress/request": "2.88.5", "@cypress/xvfb": "1.2.4", - "@types/blob-util": "1.3.3", - "@types/bluebird": "3.5.29", - "@types/chai": "4.2.7", - "@types/chai-jquery": "1.1.40", - "@types/jquery": "3.3.31", - "@types/lodash": "4.14.149", - "@types/minimatch": "3.0.3", - "@types/mocha": "5.2.7", - "@types/sinon": "7.5.1", - "@types/sinon-chai": "3.2.3", + "@types/sinonjs__fake-timers": "6.0.1", "@types/sizzle": "2.3.2", "arch": "2.1.1", "bluebird": "3.7.2", @@ -878,14 +804,6 @@ "untildify": "4.0.0", "url": "0.11.0", "yauzl": "2.10.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - } } }, "dashdash": { @@ -1081,12 +999,6 @@ "ms": "2.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mkdirp": { "version": "0.5.5", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", @@ -1859,6 +1771,12 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", diff --git a/cypress/package.json b/cypress/package.json index ee6097c02..05d99bede 100644 --- a/cypress/package.json +++ b/cypress/package.json @@ -5,11 +5,13 @@ "main": "index.js", "scripts": { "test": "cypress run", + "wait-for-server": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/", + "test:sql": "cypress run --browser chrome --headless --spec \"./integration/dataexplorer/SQL/*\"", "test:ci": "wait-on -t 240000 -i 5000 -v https-get://0.0.0.0:1234/ https-get://0.0.0.0:8081/_explorer/index.html && cypress run --browser chrome --headless", "test:debug": "cypress open" }, "devDependencies": { - "cypress": "^4.5.0", + "cypress": "^4.8.0", "mocha": "^7.0.1", "mochawesome": "^4.1.0", "mochawesome-merge": "^4.0.1", diff --git a/cypress/utilities/connectionString.js b/cypress/utilities/connectionString.js index a823a74ee..496f70111 100644 --- a/cypress/utilities/connectionString.js +++ b/cypress/utilities/connectionString.js @@ -1,56 +1,41 @@ module.exports = { - loginUsingConnectionString: function (api) { + loginUsingConnectionString: function() { + const prodUrl = Cypress.env("TEST_ENDPOINT") || "https://localhost:1234/hostedExplorer.html"; + const timeout = 15000; + cy.visit(prodUrl); + cy.get('iframe[id="explorerMenu"]').should("be.visible"); - const prodUrl = "https://cosmos.azure.com/"; - const timeout = 15000; - - cy.visit(prodUrl); - cy.get('iframe[id="explorerMenu"]').should("be.visible"); - - cy.get("iframe").then($element => { - const $body = $element.contents().find("body"); - - cy.wrap($body) - .find("#connectExplorer") - .should("exist") - .find("div[class='connectExplorer']") - .should("exist") - .find("p[class='welcomeText']") - .should("exist"); - - cy.wrap($body.find("div > p.switchConnectTypeText")) - .should("exist") - .last() - .click({ force: true }); - - const secret = Cypress.env('connectionString')[api]; + cy.get("iframe").then($element => { + const $body = $element.contents().find("body"); - cy.wrap($body) - .find("input[class='inputToken']") - .should("exist") - .type(secret, { - force: true - }); - - cy.wrap($body.find("input[value='Connect']"), { timeout }) - .first() - .click({ force: true }); - - cy.wait(15000); - - cy.wrap($body) - .find(".connectExplorer > p:nth-child(3)") - .should("be.visible"); + cy.wrap($body) + .find("#connectExplorer") + .should("exist") + .find("div[class='connectExplorer']") + .should("exist") + .find("p[class='welcomeText']") + .should("exist"); - }); - }, - constants:{ - sql: "sql", - mongo: "mongo", - table: "table", - graph: "graph", - cassandra: "cassandra" - } + cy.wrap($body.find("div > p.switchConnectTypeText")) + .should("exist") + .last() + .click({ force: true }); -} \ No newline at end of file + const secret = Cypress.env("CONNECTION_STRING"); + + cy.wrap($body) + .find("input[class='inputToken']") + .should("exist") + .type(secret, { + force: true + }); + + cy.wrap($body.find("input[value='Connect']"), { timeout }) + .first() + .click({ force: true }); + + cy.wait(15000); + }); + } +};