diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj index 3b27ad0ee0c7c9ec1abcc6cd5f435bd36999d16e..618cadd91551c7f2bfd48d2fb745864a4d601a67 100644 --- a/src/metabase/api/session.clj +++ b/src/metabase/api/session.clj @@ -174,11 +174,8 @@ (when-not (:last_login user) (email/send-user-joined-admin-notification-email! (User user-id))) ;; after a successful password update go ahead and offer the client a new session that they can use - (let [session-id (create-session! user)] - (mw.session/set-session-cookie - {:success true - :session_id (str session-id)} - session-id))) + {:success true + :session_id (create-session! user)}) (api/throw-invalid-param-exception :password (tru "Invalid reset token")))) diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj index 34db8770dfb673b615de27f0f434e6026c971562..a76b677f6c89744801b3051b74625952882b37af 100644 --- a/src/metabase/api/setup.clj +++ b/src/metabase/api/setup.clj @@ -77,7 +77,7 @@ (setup/clear-token!) ;; then we create a session right away because we want our new user logged in to continue the setup process (db/insert! Session - :id (str session-id) + :id (str session-id) :user_id (:id new-user)) ;; notify that we've got a new user in the system AND that this user logged in (events/publish-event! :user-create {:user_id (:id new-user)}) diff --git a/src/metabase/middleware/session.clj b/src/metabase/middleware/session.clj index ecb64c7b7e731674106d1493bf6a25100d2a07b4..c47a2c5418d0d09851a51ce3fdf3404081af4df9 100644 --- a/src/metabase/middleware/session.clj +++ b/src/metabase/middleware/session.clj @@ -13,8 +13,7 @@ [schema.core :as s] [toucan.db :as db]) (:import java.net.URL - java.util.UUID - org.joda.time.DateTime)) + java.util.UUID)) (def ^:private ^String metabase-session-cookie "metabase.SESSION_ID") (def ^:private ^String metabase-session-header "x-metabase-session") @@ -41,14 +40,14 @@ (defn clear-session-cookie "Add a header to `response` to clear the current Metabase session cookie." [response] - (resp/set-cookie response metabase-session-cookie nil {:expires (DateTime. 0)})) + (println "response:" response "->" (resp/set-cookie response nil {:expires "01 Jan 1970 00:00:00 GMT"})) ; NOCOMMIT + #_(resp/set-cookie response nil {:expires "01 Jan 1970 00:00:00 GMT"})) (defn- wrap-session-id* [{:keys [cookies headers] :as request}] - (let [session-id (or (get-in cookies [metabase-session-cookie :value]) - (headers metabase-session-header))] - (if (seq session-id) - (assoc request :metabase-session-id session-id) - request))) + (if-let [session-id (or (get-in cookies [metabase-session-cookie :value]) + (headers metabase-session-header))] + (assoc request :metabase-session-id session-id) + request)) (defn wrap-session-id "Middleware that sets the `:metabase-session-id` keyword on the request if a session id can be found. diff --git a/test/expectation_options.clj b/test/expectation_options.clj index 635fa87d7c302b8ce38fcdbcd79a0eaf48e4ba4b..f9dbc19aa97febe1b371e6ae586e0614ec33eec4 100644 --- a/test/expectation_options.clj +++ b/test/expectation_options.clj @@ -1,53 +1,5 @@ (ns expectation-options - "Namespace expectations will automatically load before running a tests" - (:require [clojure - [data :as data] - [set :as set]] - [expectations :as expectations] - [metabase.util :as u])) - -;;; ---------------------------------------- Expectations Framework Settings ----------------------------------------- - -;; ## EXPECTATIONS FORMATTING OVERRIDES - -;; These overrides the methods Expectations usually uses for printing failed tests. -;; These are basically the same as the original implementations, but they colorize and pretty-print the -;; output, which makes it an order of magnitude easier to read, especially for tests that compare a -;; lot of data, like Query Processor or API tests. -(defn- format-failure [e a str-e str-a] - {:type :fail - :expected-message (when-let [in-e (first (data/diff e a))] - (format "\nin expected, not actual:\n%s" (u/pprint-to-str 'green in-e))) - :actual-message (when-let [in-a (first (data/diff a e))] - (format "\nin actual, not expected:\n%s" (u/pprint-to-str 'red in-a))) - :raw [str-e str-a] - :result ["\nexpected:\n" - (u/pprint-to-str 'green e) - "\nwas:\n" - (u/pprint-to-str 'red a)]}) - -(defmethod expectations/compare-expr :expectations/maps [e a str-e str-a] - (let [[in-e in-a] (data/diff e a)] - (if (and (nil? in-e) (nil? in-a)) - {:type :pass} - (format-failure e a str-e str-a)))) - -(defmethod expectations/compare-expr :expectations/sets [e a str-e str-a] - (format-failure e a str-e str-a)) - -(defmethod expectations/compare-expr :expectations/sequentials [e a str-e str-a] - (let [diff-fn (fn [e a] (seq (set/difference (set e) (set a))))] - (assoc (format-failure e a str-e str-a) - :message (cond - (and (= (set e) (set a)) - (= (count e) (count a)) - (= (count e) (count (set a)))) "lists appear to contain the same items with different ordering" - (and (= (set e) (set a)) - (< (count e) (count a))) "some duplicate items in actual are not expected" - (and (= (set e) (set a)) - (> (count e) (count a))) "some duplicate items in expected are not actual" - (< (count e) (count a)) "actual is larger than expected" - (> (count e) (count a)) "expected is larger than actual")))) + "Namespace expectations will automatically load before running a tests") ;;; ---------------------------------------------- check-for-slow-tests ---------------------------------------------- diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj index acf23547c4d115f22d91b63c723659d870f32249..e46dbce332db07230d68b2032d6cf932cf7d4260 100644 --- a/test/metabase/api/session_test.clj +++ b/test/metabase/api/session_test.clj @@ -13,12 +13,11 @@ [metabase.test [data :refer :all] [util :as tu]] - [metabase.test.data.users :as test-users :refer :all] + [metabase.test.data.users :refer :all] [metabase.test.integrations.ldap :refer [expect-with-ldap-server]] [metabase.test.util.log :as tu.log] [toucan.db :as db] - [toucan.util.test :as tt]) - (:import java.util.UUID)) + [toucan.util.test :as tt])) ;; ## POST /api/session ;; Test that we can login @@ -62,16 +61,10 @@ ;; Test that we can logout (expect nil - (do - ;; clear out cached session tokens so next time we make an API request it log in & we'll know we have a valid - ;; Session - (test-users/clear-cached-session-tokens!) - (let [session-id (test-users/username->token :rasta)] - ;; Ok, calling the logout endpoint should delete the Session in the DB. Don't worry, `test-users` will log back - ;; in on the next API call - ((user->client :rasta) :delete 204 "session") - ;; check whether it's still there -- should be GONE - (Session session-id)))) + (let [{session_id :id} ((user->client :rasta) :post 200 "session" (user->credentials :rasta))] + (assert session_id) + ((user->client :rasta) :delete 204 "session" :session_id session_id) + (Session session_id))) ;; ## POST /api/session/forgot_password @@ -241,15 +234,18 @@ [:first_name :last_name :email])))) -;;; --------------------------------------- google-auth-fetch-or-create-user! ---------------------------------------- +;;; tests for google-auth-fetch-or-create-user! + +(defn- is-session? [session] + (u/ignore-exceptions + (tu/is-uuid-string? (:id session)))) ;; test that an existing user can log in with Google auth even if the auto-create accounts domain is different from ;; their account should return a Session (expect - UUID (tt/with-temp User [user {:email "cam@sf-toucannery.com"}] (tu/with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"] - (#'session-api/google-auth-fetch-or-create-user! "Cam" "Saul" "cam@sf-toucannery.com")))) + (is-session? (#'session-api/google-auth-fetch-or-create-user! "Cam" "Saül" "cam@sf-toucannery.com"))))) ;; test that a user that doesn't exist with a *different* domain than the auto-create accounts domain gets an ;; exception @@ -262,14 +258,11 @@ ;; test that a user that doesn't exist with the *same* domain as the auto-create accounts domain means a new user gets ;; created (expect - UUID (et/with-fake-inbox (tu/with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com" admin-email "rasta@toucans.com"] - (try - (#'session-api/google-auth-fetch-or-create-user! "Rasta" "Toucan" "rasta@sf-toucannery.com") - (finally - (db/delete! User :email "rasta@sf-toucannery.com")))))) ; clean up after ourselves + (u/prog1 (is-session? (#'session-api/google-auth-fetch-or-create-user! "Rasta" "Toucan" "rasta@sf-toucannery.com")) + (db/delete! User :email "rasta@sf-toucannery.com"))))) ; clean up after ourselves ;;; ------------------------------------------- TESTS FOR LDAP AUTH STUFF -------------------------------------------- diff --git a/test/metabase/test/data/users.clj b/test/metabase/test/data/users.clj index a714ccf73ba52d20d4d0f0eca21b1e9fb664822b..edfa9d35e6e7c34bf5a76cf6a6e8a77d51802d38 100644 --- a/test/metabase/test/data/users.clj +++ b/test/metabase/test/data/users.clj @@ -8,10 +8,8 @@ [metabase.core.initialization-status :as init-status] [metabase.middleware.session :as mw.session] [metabase.models.user :as user :refer [User]] - [schema.core :as s] [toucan.db :as db]) - (:import clojure.lang.ExceptionInfo - metabase.models.user.UserInstance)) + (:import clojure.lang.ExceptionInfo)) ;;; ------------------------------------------------ User Definitions ------------------------------------------------ @@ -44,12 +42,9 @@ :password "birdseed" :active false}}) -(def ^:private usernames +(def ^:private ^:const usernames (set (keys user->info))) -(def ^:private TestUserName - (apply s/enum usernames)) - ;;; ------------------------------------------------- Test User Fns -------------------------------------------------- (defn- wait-for-initiailization @@ -72,8 +67,8 @@ (defn- fetch-or-create-user! "Create User if they don't already exist and return User." [& {:keys [email first last password superuser active] - :or {superuser false - active true}}] + :or {superuser false + active true}}] {:pre [(string? email) (string? first) (string? last) (string? password) (m/boolean? superuser) (m/boolean? active)]} (wait-for-initiailization) (or (User :email email) @@ -87,18 +82,19 @@ :is_active active))) -(s/defn fetch-user :- UserInstance +(defn fetch-user "Fetch the User object associated with USERNAME. Creates user if needed. (fetch-user :rasta) -> {:id 100 :first_name \"Rasta\" ...}" - [username :- TestUserName] + [username] + {:pre [(contains? usernames username)]} (m/mapply fetch-or-create-user! (user->info username))) -(s/defn create-users-if-needed! +(defn create-users-if-needed! "Force creation of the test users if they don't already exist." ([] (apply create-users-if-needed! usernames)) - ([& usernames :- [TestUserName]] + ([& usernames] (doseq [username usernames] ;; fetch-user will force creation of users (fetch-user username)))) @@ -108,15 +104,15 @@ (user->id :rasta) -> 4" (memoize - (s/fn :- s/Int [username :- TestUserName] + (fn [username] {:pre [(contains? usernames username)]} - (u/get-id (fetch-user username))))) + (:id (fetch-user username))))) -(s/defn user->credentials :- {:username (s/pred u/email?), :password s/Str} +(defn user->credentials "Return a map with `:username` and `:password` for User with USERNAME. (user->credentials :rasta) -> {:username \"rasta@metabase.com\", :password \"blueberries\"}" - [username :- TestUserName] + [username] {:pre [(contains? usernames username)]} (let [{:keys [email password]} (user->info username)] {:username email @@ -132,9 +128,9 @@ (defonce ^:private tokens (atom {})) -(s/defn username->token :- u/uuid-regex +(defn username->token "Return cached session token for a test User, logging in first if needed." - [username :- TestUserName] + [username] (or (@tokens username) (u/prog1 (http/authenticate (user->credentials username)) (swap! tokens assoc username <>)) @@ -158,18 +154,18 @@ (clear-cached-session-tokens!) (apply client-fn username args))))) -(s/defn user->client :- (s/pred fn?) +(defn user->client "Returns a `metabase.http-client/client` partially bound with the credentials for User with USERNAME. In addition, it forces lazy creation of the User if needed. ((user->client) :get 200 \"meta/table\")" - [username :- TestUserName] + [username] (create-users-if-needed! username) (partial client-fn username)) -(s/defn do-with-test-user +(defn do-with-test-user "Call `f` with various `metabase.api.common` dynamic vars bound to the test User named by `user-kwd`." - [user-kwd :- TestUserName, f :- (s/pred fn?)] + [user-kwd f] ((mw.session/bind-current-user (fn [_ respond _] (respond (f)))) (let [user-id (user->id user-kwd)] {:metabase-user-id user-id diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj index c25c43af3269491db83ad9289a5e72bc07a1d498..c9b7dca0f02a933704ab5d552ae3531aa9a50021 100644 --- a/test/metabase/test/util.clj +++ b/test/metabase/test/util.clj @@ -3,11 +3,11 @@ (:require [cheshire.core :as json] [clj-time.core :as time] [clojure - [string :as str] + [string :as s] [walk :as walk]] [clojure.tools.logging :as log] [clojurewerkz.quartzite.scheduler :as qs] - [expectations :as expectations :refer [expect]] + [expectations :refer :all] [metabase [driver :as driver] [task :as task] @@ -35,35 +35,11 @@ [metabase.test.data :as data] [metabase.test.data.dataset-definitions :as defs] [metabase.util.date :as du] - [schema.core :as s] [toucan.db :as db] [toucan.util.test :as test]) (:import org.apache.log4j.Logger [org.quartz CronTrigger JobDetail JobKey Scheduler Trigger])) -;; record type for testing that results match a Schema -(defrecord SchemaExpectation [schema] - expectations/CustomPred - (expect-fn [_ actual] - (nil? (s/check schema actual))) - (expected-message [_ _ _ _] - (str "Result did not match schema:\n" - (u/pprint-to-str (s/explain schema)))) - (actual-message [_ actual _ _] - (str "Was:\n" - (u/pprint-to-str actual))) - (message [_ actual _ _] - (u/pprint-to-str (s/check schema actual)))) - -(defmacro expect-schema - "Like `expect`, but checks that results match a schema." - {:style/indent 0} - [expected actual] - `(expect - (SchemaExpectation. ~expected) - ~actual)) - - ;;; ---------------------------------------------------- match-$ ----------------------------------------------------- (defn- $->prop @@ -132,8 +108,8 @@ (every-pred (some-fn keyword? string?) (some-fn #{:id :created_at :updated_at :last_analyzed :created-at :updated-at :field-value-id :field-id :fields_hash :date_joined :date-joined :last_login :dimension-id :human-readable-field-id} - #(str/ends-with? % "_id") - #(str/ends-with? % "_at"))) + #(s/ends-with? % "_id") + #(s/ends-with? % "_at"))) data)) ([pred data] (walk/prewalk (fn [maybe-map] diff --git a/test/metabase/test_setup.clj b/test/metabase/test_setup.clj index ed47f29be45afdbc1bace2c24287cb9bc747fe9e..8d23f9cbad0f968c106e12e004ed2779dcdbe8f3 100644 --- a/test/metabase/test_setup.clj +++ b/test/metabase/test_setup.clj @@ -1,8 +1,12 @@ (ns metabase.test-setup "Functions that run before + after unit tests (setup DB, start web server, load test data)." - (:require [clojure.java.io :as io] - [clojure.string :as str] + (:require [clojure + [data :as data] + [set :as set] + [string :as str]] + [clojure.java.io :as io] [clojure.tools.logging :as log] + [expectations :refer :all] [metabase [db :as mdb] [handler :as handler] @@ -16,6 +20,50 @@ [metabase.test.data.env :as tx.env] [yaml.core :as yaml])) +;;; ---------------------------------------- Expectations Framework Settings ----------------------------------------- + +;; ## EXPECTATIONS FORMATTING OVERRIDES + +;; These overrides the methods Expectations usually uses for printing failed tests. +;; These are basically the same as the original implementations, but they colorize and pretty-print the +;; output, which makes it an order of magnitude easier to read, especially for tests that compare a +;; lot of data, like Query Processor or API tests. +(defn- format-failure [e a str-e str-a] + {:type :fail + :expected-message (when-let [in-e (first (data/diff e a))] + (format "\nin expected, not actual:\n%s" (u/pprint-to-str 'green in-e))) + :actual-message (when-let [in-a (first (data/diff a e))] + (format "\nin actual, not expected:\n%s" (u/pprint-to-str 'red in-a))) + :raw [str-e str-a] + :result ["\nexpected:\n" + (u/pprint-to-str 'green e) + "\nwas:\n" + (u/pprint-to-str 'red a)]}) + +(defmethod compare-expr :expectations/maps [e a str-e str-a] + (let [[in-e in-a] (data/diff e a)] + (if (and (nil? in-e) (nil? in-a)) + {:type :pass} + (format-failure e a str-e str-a)))) + +(defmethod compare-expr :expectations/sets [e a str-e str-a] + (format-failure e a str-e str-a)) + +(defmethod compare-expr :expectations/sequentials [e a str-e str-a] + (let [diff-fn (fn [e a] (seq (set/difference (set e) (set a))))] + (assoc (format-failure e a str-e str-a) + :message (cond + (and (= (set e) (set a)) + (= (count e) (count a)) + (= (count e) (count (set a)))) "lists appear to contain the same items with different ordering" + (and (= (set e) (set a)) + (< (count e) (count a))) "some duplicate items in actual are not expected" + (and (= (set e) (set a)) + (> (count e) (count a))) "some duplicate items in expected are not actual" + (< (count e) (count a)) "actual is larger than expected" + (> (count e) (count a)) "expected is larger than actual")))) + + ;;; ------------------------------- Functions That Get Ran On Test Suite Start / Stop -------------------------------- (defn- driver-plugin-manifest [driver] diff --git a/test/metabase/util_test.clj b/test/metabase/util_test.clj index d84afec808059a58d567c1795fa9d95b65c1bdf2..5a278d0aea3d19e0e3bef6fc28646f23408ab94c 100644 --- a/test/metabase/util_test.clj +++ b/test/metabase/util_test.clj @@ -65,7 +65,7 @@ (expect "cam_s_awesome_toucan_emporium" (slugify "Cam's awesome toucan emporium")) (expect "frequently_used_cards" (slugify "Frequently-Used Cards")) ;; check that diactrics get removed -(expect "cam_saul_s_toucannery" (slugify "Cam Saul's Toucannery")) +(expect "cam_saul_s_toucannery" (slugify "Cam Saül's Toucannery")) (expect "toucans_dislike_pinatas___" (slugify "toucans dislike piñatas :(")) ;; check that non-ASCII characters get URL-encoded (so we can support non-Latin alphabet languages; see #3818) (expect "%E5%8B%87%E5%A3%AB" (slugify "勇士")) ; go dubs