diff --git a/src/metabase/setup.clj b/src/metabase/setup.clj index b58bbe38c59ecc04e09d9b8dfb822dc198698e66..bddc4b3eea9d53fd19721456823f12eff8102be6 100644 --- a/src/metabase/setup.clj +++ b/src/metabase/setup.clj @@ -1,9 +1,12 @@ (ns metabase.setup - (:require [environ.core :as env] - [metabase.models.setting :as setting :refer [defsetting Setting]] - [metabase.models.user :refer [User]] - [toucan.db :as db]) - (:import java.util.UUID)) + (:require + [environ.core :as env] + [metabase.db.connection :as mdb.connection] + [metabase.models.setting :as setting :refer [defsetting Setting]] + [metabase.models.user :refer [User]] + [toucan.db :as db]) + (:import + (java.util UUID))) (defsetting setup-token "A token used to signify that an instance has permissions to create the initial User. This is created upon the first @@ -39,8 +42,13 @@ ;; Once a User is created it's impossible for this to ever become falsey -- deleting the last User is disallowed. ;; After this returns true once the result is cached and it will continue to return true forever without any ;; additional DB hits. - :getter (fn [] - (let [user-exists? (atom false)] - (or @user-exists? - (reset! user-exists? (db/exists? User))))) + ;; + ;; This is keyed by the unique identifier for the application database, to support resetting it in tests or swapping + ;; it out in the REPL + :getter (let [app-db-id->user-exists? (atom {})] + (fn [] + (or (get @app-db-id->user-exists? (mdb.connection/unique-identifier)) + (let [exists? (db/exists? User)] + (swap! app-db-id->user-exists? assoc (mdb.connection/unique-identifier) exists?) + exists?)))) :doc false) diff --git a/test/metabase/setup_test.clj b/test/metabase/setup_test.clj new file mode 100644 index 0000000000000000000000000000000000000000..e2fb0fa3ebd296887dc72179e7a2843d48d3bc1b --- /dev/null +++ b/test/metabase/setup_test.clj @@ -0,0 +1,39 @@ +(ns metabase.setup-test + (:require + [clojure.test :refer :all] + [metabase.db :as mdb] + [metabase.db.schema-migrations-test.impl + :as schema-migrations-test.impl] + [metabase.setup :as setup] + [metabase.test :as mt] + [toucan.db :as db])) + +(deftest has-user-setup-cached-test + (testing "The has-user-setup getter should cache truthy results since it can never become falsey" + ;; make sure some test users are created. + (mt/initialize-if-needed! :test-users) + (db/with-call-counting [call-count] + ;; call has-user-setup several times. + (dotimes [_ 5] + (is (= true + (setup/has-user-setup)))) + ;; `has-user-setup` should have done at most one application database call, as opposed to one call per call to + ;; the getter + (is (contains? #{0 1} (call-count))))) + (testing "Return falsey for an empty instance. Values should be cached for current app DB to support swapping in tests/REPL" + ;; create a new completely empty database. + (schema-migrations-test.impl/with-temp-empty-app-db [_conn :h2] + ;; make sure the DB is setup (e.g., run all the Liquibase migrations) + (mdb/setup-db!) + (db/with-call-counting [call-count] + (dotimes [_ 5] + (is (= false + (setup/has-user-setup)))) + (testing "Should continue doing new DB calls as long as there is no User" + (is (= 5 + (call-count))))))) + (testing "Switch back to the 'normal' app DB; value should still be cached for it" + (db/with-call-counting [call-count] + (is (= true + (setup/has-user-setup))) + (is (zero? (call-count))))))