From 06f14c0126aeab927ddee542f851a950d5edeca3 Mon Sep 17 00:00:00 2001
From: "Damon P. Cortesi" <d.lifehacker@gmail.com>
Date: Wed, 15 Apr 2020 07:20:45 -0700
Subject: [PATCH] Feature/cypress mongo (#12292)

* Add database-specific tests to Cypress (starting with Mongo)
* Initial changes to Cypress to allow a limited set of tests to run
* Include drivers in frontend uberjar
---
 .circleci/config.yml                          |  45 ++++++-
 frontend/test/__runner__/run_cypress_tests.js |  18 ++-
 frontend/test/__support__/cypress.js          |  11 ++
 frontend/test/cypress.json                    |   2 +-
 .../test/metabase-db/mongo/add.cy.spec.js     |  58 +++++++++
 .../test/metabase-db/mongo/query.cy.spec.js   | 110 ++++++++++++++++++
 6 files changed, 236 insertions(+), 8 deletions(-)
 create mode 100644 frontend/test/metabase-db/mongo/add.cy.spec.js
 create mode 100644 frontend/test/metabase-db/mongo/query.cy.spec.js

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 59b9c85b047..6f4181a5efa 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -170,6 +170,11 @@ executors:
           ACCEPT_EULA: Y
           SA_PASSWORD: 'P@ssw0rd'
 
+  fe-mongo:
+    working_directory: /home/circleci/metabase/metabase/
+    docker:
+      - image: circleci/clojure:lein-2.8.1-node-browsers
+      - image: circleci/mongo:4.0
 
 
 ########################################################################################################################
@@ -279,6 +284,13 @@ jobs:
             done;
             echo `md5sum yarn.lock` >> frontend-checksums.txt
             echo `md5sum webpack.config.js` >> frontend-checksums.txt
+      # As well as driver modules (database drivers)
+      - run:
+          name: Generate checksums of all driver module source files to use as Uberjar cache key
+          command: >
+            for file in `find ./modules -type f | sort`;
+              do echo `md5sum $file` >> modules-checksums.txt;
+            done;
       - run:
           name: Save last git commit message
           command: git log -1 --oneline > commit.txt
@@ -530,18 +542,18 @@ jobs:
       - restore-be-deps-cache
       - restore_cache:
           keys:
-            - uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}
+            - uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
       - run:
           name: Build uberjar if needed
           command: >
             if [ ! -f './target/uberjar/metabase.jar' ];
-              then ./bin/build version frontend uberjar;
+              then ./bin/build version frontend drivers uberjar;
             fi
           no_output_timeout: 5m
       - store_artifacts:
           path: /home/circleci/metabase/metabase/target/uberjar/metabase.jar
       - save_cache:
-          key: uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}
+          key: uberjar-{{ checksum "./backend-checksums.txt" }}-{{ checksum "./frontend-checksums.txt" }}-{{ checksum "./modules-checksums.txt" }}
           paths:
             - /home/circleci/metabase/metabase/target/uberjar/metabase.jar
 
@@ -560,11 +572,24 @@ jobs:
                 command: ./bin/build version
 
   fe-tests-cypress:
-    executor: clojure-and-node
+    parameters:
+      e:
+        type: executor
+        default: clojure-and-node
+      only-single-database:
+        type: boolean
+        default: false
+      test-files-location:
+        type: string
+        default: ""
+      driver:
+        type: string
+        default: ""
+    executor: << parameters.e >>
     steps:
       - run-yarn-command:
           command-name: Run Cypress tests
-          command: run test-cypress-no-build
+          command: run test-cypress-no-build <<# parameters.only-single-database >> --testFiles << parameters.test-files-location >> <</ parameters.only-single-database >>
           before-steps:
             - restore_cache:
                 keys:
@@ -852,6 +877,16 @@ workflows:
             - build-uberjar
             - fe-deps
 
+      - fe-tests-cypress:
+          name: fe-tests-cypress-mongo
+          requires:
+            - build-uberjar
+            - fe-deps
+          e: fe-mongo
+          driver: mongo
+          only-single-database: true
+          test-files-location: frontend/test/metabase-db/mongo
+
       - deploy-master:
           requires:
             - yaml-linter
diff --git a/frontend/test/__runner__/run_cypress_tests.js b/frontend/test/__runner__/run_cypress_tests.js
index 1644bc2af47..20019ae94c2 100644
--- a/frontend/test/__runner__/run_cypress_tests.js
+++ b/frontend/test/__runner__/run_cypress_tests.js
@@ -7,8 +7,13 @@ const BackendResource = require("./backend.js").BackendResource;
 
 const server = BackendResource.get({ dbKey: "" });
 
+// We currently accept two (optional) command line arguments
+// --open - Opens the Cypress test browser
+// --testFiles <path> - Specifies a different path for the integration folder
 const userArgs = process.argv.slice(2);
-const isOpenMode = userArgs[0] === "--open";
+const isOpenMode = userArgs.includes("--open");
+const testFiles = userArgs.includes("--testFiles");
+const testFilesLocation = userArgs[userArgs.indexOf("--testFiles") + 1];
 
 function readFile(fileName) {
   return new Promise(function(resolve, reject) {
@@ -30,6 +35,10 @@ const init = async () => {
     );
   }
 
+  if (testFiles) {
+    console.log(chalk.bold(`Running tests in '${testFilesLocation}'`));
+  }
+
   try {
     const version = await readFile(
       __dirname + "/../../../resources/version.properties",
@@ -54,6 +63,11 @@ const init = async () => {
   await BackendResource.start(server);
 
   console.log(chalk.bold("Starting Cypress"));
+  let commandLineConfig = `baseUrl=${server.host}`;
+  if (testFiles) {
+    commandLineConfig = `${commandLineConfig},integrationFolder=${testFilesLocation}`;
+  }
+
   const cypressProcess = spawn(
     "yarn",
     [
@@ -62,7 +76,7 @@ const init = async () => {
       "--config-file",
       process.env["CONFIG_FILE"],
       "--config",
-      `baseUrl=${server.host}`,
+      commandLineConfig,
       ...(process.env["CI"]
         ? [
             "--reporter",
diff --git a/frontend/test/__support__/cypress.js b/frontend/test/__support__/cypress.js
index 94c8ae103e4..075eecfc998 100644
--- a/frontend/test/__support__/cypress.js
+++ b/frontend/test/__support__/cypress.js
@@ -68,6 +68,8 @@ export function main() {
   return cy.get("nav").next();
 }
 
+// Metabase utility functions for commonly-used patterns
+
 export function openOrdersTable() {
   cy.visit("/question/new?database=1&table=2");
 }
@@ -76,4 +78,13 @@ export function openProductsTable() {
   cy.visit("/question/new?database=1&table=1");
 }
 
+// Find a text field by label text, type it in, then blur the field.
+// Commonly used in our Admin section as we auto-save settings.
+export function typeAndBlurUsingLabel(label, value) {
+  cy.findByLabelText(label)
+    .clear()
+    .type(value)
+    .blur();
+}
+
 Cypress.on("uncaught:exception", (err, runnable) => false);
diff --git a/frontend/test/cypress.json b/frontend/test/cypress.json
index e026f5171fd..2d6709c27b8 100644
--- a/frontend/test/cypress.json
+++ b/frontend/test/cypress.json
@@ -1,7 +1,7 @@
 {
   "testFiles": "**/*.cy.spec.js",
   "pluginsFile": "frontend/test/cypress-plugins.js",
-  "integrationFolder": "frontend/test",
+  "integrationFolder": "frontend/test/metabase",
   "supportFile": "frontend/test/__support__/cypress.js",
   "viewportHeight": 800,
   "viewportWidth": 1280
diff --git a/frontend/test/metabase-db/mongo/add.cy.spec.js b/frontend/test/metabase-db/mongo/add.cy.spec.js
new file mode 100644
index 00000000000..dde062aa147
--- /dev/null
+++ b/frontend/test/metabase-db/mongo/add.cy.spec.js
@@ -0,0 +1,58 @@
+import {
+  signInAsAdmin,
+  restore,
+  modal,
+  typeAndBlurUsingLabel,
+} from "__support__/cypress";
+
+function addMongoDatabase() {
+  cy.visit("/admin/databases/create");
+  cy.contains("Database type")
+    .closest(".Form-field")
+    .find("a")
+    .click();
+  cy.contains("MongoDB").click({ force: true });
+  cy.contains("Additional Mongo connection");
+
+  typeAndBlurUsingLabel("Name", "MongoDB");
+  typeAndBlurUsingLabel("Database name", "admin");
+
+  cy.findByText("Save")
+    .should("not.be.disabled")
+    .click();
+}
+
+describe("mongodb > admin > add", () => {
+  beforeEach(() => {
+    restore();
+    signInAsAdmin();
+    cy.server();
+  });
+
+  it("should add a database and redirect to listing", () => {
+    cy.route({
+      method: "POST",
+      url: "/api/database",
+    }).as("createDatabase");
+
+    addMongoDatabase();
+
+    cy.wait("@createDatabase");
+
+    cy.url().should("match", /\/admin\/databases\?created=\d+$/);
+    cy.contains("Your database has been added!");
+    modal()
+      .contains("I'm good thanks")
+      .click();
+  });
+
+  it("can query a Mongo database", () => {
+    addMongoDatabase();
+    cy.url().should("match", /\/admin\/databases\?created=\d+$/);
+    cy.visit("/question/new");
+    cy.contains("Simple question").click();
+    cy.contains("MongoDB").click();
+    cy.contains("Version").click();
+    cy.contains("featureCompatibilityVersion");
+  });
+});
diff --git a/frontend/test/metabase-db/mongo/query.cy.spec.js b/frontend/test/metabase-db/mongo/query.cy.spec.js
new file mode 100644
index 00000000000..e156f3dfafa
--- /dev/null
+++ b/frontend/test/metabase-db/mongo/query.cy.spec.js
@@ -0,0 +1,110 @@
+import {
+  signInAsAdmin,
+  restore,
+  modal,
+  signInAsNormalUser,
+} from "__support__/cypress";
+
+function addMongoDatabase() {
+  cy.request("POST", "/api/database", {
+    engine: "mongo",
+    name: "MongoDB",
+    details: {
+      host: "localhost",
+      dbname: "admin",
+      port: 27017,
+      user: null,
+      pass: null,
+      authdb: null,
+      "additional-options": null,
+      "use-srv": false,
+      "tunnel-enabled": false,
+    },
+    auto_run_queries: true,
+    is_full_sync: true,
+    schedules: {
+      cache_field_values: {
+        schedule_day: null,
+        schedule_frame: null,
+        schedule_hour: 0,
+        schedule_type: "daily",
+      },
+      metadata_sync: {
+        schedule_day: null,
+        schedule_frame: null,
+        schedule_hour: null,
+        schedule_type: "hourly",
+      },
+    },
+  });
+}
+
+describe("mongodb > user > query", () => {
+  before(() => {
+    restore();
+    signInAsAdmin();
+    addMongoDatabase();
+  });
+
+  beforeEach(() => {
+    signInAsNormalUser();
+  });
+
+  it("can query a Mongo database as a user", () => {
+    cy.visit("/question/new");
+    cy.contains("Simple question").click();
+    cy.contains("MongoDB").click();
+    cy.contains("Version").click();
+    cy.contains("featureCompatibilityVersion");
+  });
+
+  it.only("can write a native MongoDB query", () => {
+    cy.visit("/question/new");
+    cy.contains("Native query").click();
+    cy.contains("MongoDB").click();
+
+    cy.get(".ace_content").type(`[ { $count: "Total" } ]`, {
+      parseSpecialCharSequences: false,
+    });
+    cy.get(".NativeQueryEditor .Icon-play").click();
+    cy.contains("1");
+  });
+
+  it("can save a native MongoDB query", () => {
+    cy.server();
+    cy.route("POST", "/api/card").as("createQuestion");
+
+    cy.visit("/question/new");
+    cy.contains("Native query").click();
+    cy.contains("MongoDB").click();
+
+    cy.get(".ace_content").type(`[ { $count: "Total" } ]`, {
+      parseSpecialCharSequences: false,
+    });
+    cy.get(".NativeQueryEditor .Icon-play").click();
+    cy.contains("1");
+
+    // Close the Ace editor because it interferes with the modal for some reason
+    cy.get(".Icon-contract").click();
+
+    cy.contains("Save").click();
+    modal()
+      .findByLabelText("Name")
+      .focus()
+      .type("mongo count");
+    modal()
+      .contains("button", "Save")
+      .should("not.be.disabled")
+      .click();
+
+    cy.wait("@createQuestion").then(({ status }) => {
+      expect(status).to.equal(202);
+    });
+
+    modal()
+      .contains("Not now")
+      .click();
+
+    cy.url().should("match", /\/question\/\d+$/);
+  });
+});
-- 
GitLab