Newer
Older
(ns metabase-enterprise.sandbox.api.field-test
"Tests for special behavior of `/api/metabase/field` endpoints in the Metabase Enterprise Edition."
(:require [clojure.test :refer :all]
[metabase-enterprise.sandbox.test-util :as mt.tu]
[metabase.models :refer [Field FieldValues User]]
[metabase.models.field-values :as field-values]
[metabase.test :as mt]
[toucan.db :as db]))
(deftest fetch-field-test
(testing "GET /api/field/:id"
(mt/with-gtaps {:gtaps {:venues {:query (mt.tu/restricted-column-query (mt/id))
:remappings {:cat [:variable [:field-id (mt/id :venues :category_id)]]}}}
:attributes {:cat 50}}
(testing "Can I fetch a Field that I don't have read access for if I have segmented table access for it?"
(let [result (mt/user-http-request :rasta :get 200 (str "field/" (mt/id :venues :name)))]
(is (map? result))
(is (= {:name "NAME"
:display_name "Name"
:has_field_values "list"}
(select-keys result [:name :display_name :has_field_values]))))))))
(deftest field-values-test
(testing "GET /api/field/:id/values"
Ngoc Khuat
committed
(mt/with-temp-copy-of-db
(doseq [[query-type gtap-rule]
[["MBQL"
{:gtaps {:venues {:query (mt.tu/restricted-column-query (mt/id))
:remappings {:cat [:dimension (mt/id :venues :category_id)]}}}
:attributes {:cat 50}}]
["native"
{:gtaps {:venues {:query
(mt/native-query
{:query "SELECT id, name, category_id FROM venues WHERE category_id = {{cat}}"
:template-tags {"cat" {:id "__MY_CAT__"
:name "cat"
:display-name "Cat id"
:type :number}}})
:remappings {:cat [:variable [:template-tag "cat"]]}}}
:attributes {:cat 50}}]]]
(testing (format "GTAP rule is a %s query" query-type)
(mt/with-gtaps gtap-rule
(testing (str "When I call the FieldValues API endpoint for a Field that I have segmented table access only "
"for, will I get ad-hoc values?\n")
(letfn [(fetch-values [user field]
(-> (mt/user-http-request user :get 200 (format "field/%d/values" (mt/id :venues field)))
(update :values (partial take 3))))]
;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. n
;; fetching FieldValues for `venue.name` should do an ad-hoc fetch and only return the names of venues in
;; that category.
(is (= {:field_id (mt/id :venues :name)
:values [["Garaje"]
["Gordo Taqueria"]
["La Tortilla"]]
:has_more_values false}
(fetch-values :rasta :name)))
Ngoc Khuat
committed
(testing (str "Now in this case recall that the `restricted-column-query` GTAP we're using does *not* include "
"`venues.price` in the results. (Toucan isn't allowed to know the number of dollar signs!) So "
"make sure if we try to fetch the field values instead of seeing `[[1] [2] [3] [4]]` we get no "
"results")
(is (= {:field_id (mt/id :venues :price)
:values []
:has_more_values false}
(fetch-values :rasta :price))))
Ngoc Khuat
committed
(testing "Reset field values; if another User fetches them first, do I still see sandboxed values? (metabase/metaboat#128)"
(field-values/clear-field-values-for-field! (mt/id :venues :name))
;; fetch Field values with an admin
(testing "Admin should see all Field values"
(is (= {:field_id (mt/id :venues :name)
:values [["20th Century Cafe"]
["25°"]
["33 Taps"]]
:has_more_values false}
(fetch-values :crowberto :name))))
(testing "Sandboxed User should still see only their values after an admin fetches the values"
(is (= {:field_id (mt/id :venues :name)
Ngoc Khuat
committed
:values [["Garaje"]
["Gordo Taqueria"]
["La Tortilla"]]
Ngoc Khuat
committed
(fetch-values :rasta :name))))
(testing "A User with a *different* sandbox should see their own values"
(let [password (mt/random-name)]
(mt/with-temp User [another-user {:password password}]
(mt/with-gtaps-for-user another-user {:gtaps {:venues
{:remappings
{:cat
[:dimension (mt/id :venues :category_id)]}}}
:attributes {:cat 5 #_BBQ}}
(is (= {:field_id (mt/id :venues :name)
:values [["Baby Blues BBQ"]
["Bludso's BBQ"]
["Boneyard Bistro"]]
:has_more_values false}
(-> (mt/client {:username (:email another-user), :password password}
:get 200
(format "field/%d/values" (mt/id :venues :name)))
(update :values (partial take 3))))))))))))))))))
(deftest human-readable-values-test
(testing "GET /api/field/:id/values should returns correct human readable mapping if exists"
(mt/with-temp-copy-of-db
(let [field-id (mt/id :venues :price)
full-fv-id (db/select-one-id FieldValues :field_id field-id :type :full)]
(db/update! FieldValues full-fv-id
:human_readable_values ["$" "$$" "$$$" "$$$$"])
;; sanity test without gtap
(is (= [[1 "$"] [2 "$$"] [3 "$$$"] [4 "$$$$"]]
(:values (mt/user-http-request :rasta :get 200 (format "field/%d/values" field-id)))))
(mt/with-gtaps {:gtaps {:venues
{:remappings {:cat [:variable [:field-id (mt/id :venues :category_id)]]}}}
:attributes {:cat 4}}
(is (= [[1 "$"] [3 "$$$"]]
(:values (mt/user-http-request :rasta :get 200 (format "field/%d/values" (mt/id :venues :price)))))))))))
(deftest search-test
(testing "GET /api/field/:id/search/:search-id"
(mt/with-gtaps {:gtaps {:venues
{:remappings {:cat [:variable [:field-id (mt/id :venues :category_id)]]}
:query (mt.tu/restricted-column-query (mt/id))}}
:attributes {:cat 50}}
(testing (str "Searching via the query builder needs to use a GTAP when the user has segmented permissions. "
"This tests out a field search on a table with segmented permissions")
;; Rasta Toucan is only allowed to see Venues that are in the "Mexican" category [category_id = 50]. So
;; searching whould only include venues in that category
(let [url (format "field/%s/search/%s" (mt/id :venues :name) (mt/id :venues :name))]
Tim Macdonald
committed
(is (= [["Gordo Taqueria" "Gordo Taqueria"]
["Tacos Villa Corona" "Tacos Villa Corona"]
["Taqueria Los Coyotes" "Taqueria Los Coyotes"]
Tim Macdonald
committed
["Taqueria San Francisco" "Taqueria San Francisco"]
["Tito's Tacos" "Tito's Tacos"]
["Yuca's Taqueria" "Yuca's Taqueria"]]
(mt/user-http-request :rasta :get 200 url :value "Ta"))))))))
(deftest caching-test
(mt/with-gtaps {:gtaps
{:venues
{:remappings {:cat [:variable [:field-id (mt/id :venues :category_id)]]}
:query (mt.tu/restricted-column-query (mt/id))}}
:attributes {:cat 50}}
(let [field (db/select-one Field :id (mt/id :venues :name))]
(field-values/get-or-create-full-field-values! field)
(mt/user-http-request :rasta :get 200 (str "field/" (:id field) "/values"))
(testing "Do we use cached values when available?"
(with-redefs [field-values/distinct-values (fn [_] (assert false "Should not be called"))]
(is (some? (:values (mt/user-http-request :rasta :get 200 (str "field/" (:id field) "/values")))))
(is (= 1 (db/count FieldValues
:field_id (:id field)
:type :sandbox)))))
(testing "Do different users has different sandbox FieldValues"
(let [password (mt/random-name)]
(mt/with-temp User [another-user {:password password}]
(mt/with-gtaps-for-user another-user {:gtaps {:venues
{:remappings {:cat [:variable [:field-id (mt/id :venues :category_id)]]}
:query (mt.tu/restricted-column-query (mt/id))}}
:attributes {:cat 5}}
(mt/user-http-request another-user :get 200 (str "field/" (:id field) "/values"))
;; create another one for the new user
(is (= 2 (db/count FieldValues
:field_id (:id field)
:type :sandbox)))))))
(testing "Do we invalidate the cache when full FieldValues change"
(let [;; Updating FieldValues which should invalidate the cache
fv-id (db/select-one-id FieldValues :field_id (:id field) :type :full)
new-values ["foo" "bar"]]
(testing "Sanity check: make sure FieldValues exist"
(is (some? fv-id)))
(db/update! FieldValues fv-id
(with-redefs [field-values/distinct-values (constantly {:values new-values
:has_more_values false})]
(:values (mt/user-http-request :rasta :get 200 (str "field/" (:id field) "/values")))))))
(finally
;; Put everything back as it was
(field-values/get-or-create-full-field-values! field))))
(testing "When a sandbox fieldvalues expired, do we delete it then create a new one?"
(#'field-values/clear-advanced-field-values-for-field! field)
;; make sure we have a cache
(mt/user-http-request :rasta :get 200 (str "field/" (:id field) "/values"))
(let [old-sandbox-fv-id (db/select-one-id FieldValues :field_id (:id field) :type :sandbox)]
(with-redefs [field-values/advanced-field-values-expired? (fn [fv]
(= (:id fv) old-sandbox-fv-id))]
(mt/user-http-request :rasta :get 200 (str "field/" (:id field) "/values"))
;; did the old one get deleted?
(is (not (db/exists? FieldValues :id old-sandbox-fv-id)))
;; make sure we created a new one
(is (= 1 (db/count FieldValues :field_id (:id field) :type :sandbox)))))))))