diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index fad16ea34edf17a55a6b9ea8a99a9b9c6ec8919d..0caacded8689f7deea51bf0aae3850c30d82e7d4 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -34,12 +34,10 @@ ;; ;; TODO -- these should all be made thread safe if possible or renamed so they end in `!`. Then we can remove them ;; from this list because by default anything ending in `!` is considered to be thread-unsafe - :disallowed + :parallel/unsafe #{clojure.core/alter-var-root clojure.core/with-redefs clojure.core/with-redefs-fn - metabase-enterprise.sandbox.test-util/with-user-attributes - metabase-enterprise.test/with-user-attributes metabase.actions.test-util/with-actions metabase.actions.test-util/with-actions-disabled metabase.actions.test-util/with-actions-enabled @@ -51,13 +49,14 @@ metabase.email-test/with-fake-inbox metabase.test.data.users/with-group metabase.test.data.users/with-group-for-user - metabase.test.persistence/with-persistence-enabled + metabase.test.data/with-empty-h2-app-db metabase.test.util.log/with-log-level metabase.test.util.misc/with-single-admin-user metabase.test.util/with-all-users-permission metabase.test.util/with-discarded-collections-perms-changes metabase.test.util/with-env-keys-renamed-by metabase.test.util/with-locale + metabase.test.util/with-model-cleanup metabase.test.util/with-non-admin-groups-no-root-collection-for-namespace-perms metabase.test.util/with-non-admin-groups-no-root-collection-perms metabase.test.util/with-temp-env-var-value @@ -73,6 +72,7 @@ metabase.test/with-actions-test-data-tables metabase.test/with-all-users-permission metabase.test/with-discarded-collections-perms-changes + metabase.test/with-empty-h2-app-db metabase.test/with-env-keys-renamed-by metabase.test/with-expected-messages metabase.test/with-fake-inbox @@ -80,9 +80,9 @@ metabase.test/with-group-for-user metabase.test/with-locale metabase.test/with-log-level + metabase.test/with-model-cleanup metabase.test/with-non-admin-groups-no-root-collection-for-namespace-perms metabase.test/with-non-admin-groups-no-root-collection-perms - metabase.test/with-persistence-enabled metabase.test/with-single-admin-user metabase.test/with-temp-env-var-value metabase.test/with-temp-vals-in-db @@ -91,7 +91,7 @@ metabase.test/with-user-in-groups} ;; these functions are allowed in `^:parallel` tests even tho they end in `!` - :allowed + :parallel/safe #{clojure.core/assoc! clojure.core/compare-and-set! clojure.core/conj! @@ -735,79 +735,79 @@ toucan.models models}}} :lint-as - {cljs.cache/defcache clojure.core/deftype - clojure.core.cache/defcache clojure.core/deftype - clojure.core.logic/defne clj-kondo.lint-as/def-catch-all - clojure.test.check.clojure-test/defspec clojure.test/deftest - clojurewerkz.quartzite.jobs/defjob clojure.core/defn - metabase-enterprise.serialization.test-util/with-dbs clojure.core/fn - metabase-enterprise.serialization.test-util/with-random-dump-dir clojure.core/let - metabase.actions.test-util/with-actions clojure.core/let - metabase.api.common/defroutes clojure.core/def - metabase.api.common/let-404 clojure.core/let - metabase.api.search-test/do-test-users clojure.core/let - metabase.async.api-response-test/with-response clojure.core/let - metabase.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let - metabase.db.custom-migrations/define-migration clj-kondo.lint-as/def-catch-all - metabase.db.custom-migrations/define-reversible-migration clj-kondo.lint-as/def-catch-all - metabase.db.data-migrations/defmigration clojure.core/def - metabase.db.liquibase/with-liquibase clojure.core/let - metabase.db.schema-migrations-test.impl/with-temp-empty-app-db clojure.core/let - metabase.driver.mongo.connection/with-mongo-client clojure.core/let - metabase.driver.mongo.connection/with-mongo-database clojure.core/let - metabase.driver.mongo.query-processor/mongo-let clojure.core/let - metabase.driver.sql-jdbc.actions/with-jdbc-transaction clojure.core/let - metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection clojure.core/let - metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info clojure.core/fn - metabase.integrations.ldap/with-ldap-connection clojure.core/fn - metabase.models.collection-test/with-collection-in-location clojure.core/let - metabase.models.json-migration/def-json-migration clj-kondo.lint-as/def-catch-all - metabase.models.setting.multi-setting/define-multi-setting clojure.core/def - metabase.models.setting/defsetting clj-kondo.lint-as/def-catch-all - metabase.public-settings.premium-features/defenterprise-schema clj-kondo.lint-as/def-catch-all - metabase.public-settings.premium-features/define-premium-feature clojure.core/def - metabase.pulse-test/with-pulse-for-card clojure.core/let - metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/-> - metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/-> - metabase.query-processor.error-type/deferror clojure.core/def - metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let - metabase.query-processor.middleware.process-userland-query-test/with-query-execution clojure.core/let - metabase.query-processor.setup/with-qp-setup clojure.core/let - metabase.shared.util.namespaces/import-fns potemkin/import-vars - metabase.sync.util/sum-for clojure.core/for - metabase.sync.util/with-emoji-progress-bar clojure.core/let - metabase.test.data.interface/defdataset clojure.core/def - metabase.test.data.interface/defdataset-edn clojure.core/def - metabase.test/defdataset clojure.core/def - metabase.test/let-url clojure.core/let - metabase.test/with-actions clojure.core/let - metabase.test/with-grouper-batches! clojure.core/fn - metabase.test/with-open-channels clojure.core/let - metabase.test/with-single-admin-user clojure.core/fn - metabase.test/with-temp-dir clojure.core/let - metabase.test/with-temp-empty-app-db clojure.core/let - metabase.test/with-temp-file clojure.core/let - metabase.test/with-user-in-groups clojure.core/let - metabase.upload-test/with-upload-table! clojure.core/let - metabase.util.files/with-open-path-to-resource clojure.core/let - metabase.util.malli.defn/defn schema.core/defn - metabase.util.malli.defn/defn- schema.core/defn - metabase.util.malli.fn/fn schema.core/fn - metabase.util.malli/defmethod schema.core/defmethod - metabase.util.malli/defn schema.core/defn - metabase.util.malli/defn- schema.core/defn - metabase.util.malli/fn schema.core/fn - metabase.util.ssh/with-ssh-tunnel clojure.core/let - metabase.xrays.domain-entities.malli/defn schema.core/defn - monger.operators/defoperator clojure.core/def - potemkin.types/defprotocol+ clojure.core/defprotocol - potemkin.types/defrecord+ clojure.core/defrecord - potemkin.types/deftype+ clojure.core/deftype - potemkin/defprotocol+ clojure.core/defprotocol - potemkin/defrecord+ clojure.core/defrecord - potemkin/deftype+ clojure.core/deftype - toucan.db/with-call-counting clojure.core/fn - toucan2.core/with-call-count clojure.core/fn} + {cljs.cache/defcache clojure.core/deftype + clojure.core.cache/defcache clojure.core/deftype + clojure.core.logic/defne clj-kondo.lint-as/def-catch-all + clojure.test.check.clojure-test/defspec clojure.test/deftest + clojurewerkz.quartzite.jobs/defjob clojure.core/defn + metabase-enterprise.serialization.test-util/with-dbs clojure.core/fn + metabase-enterprise.serialization.test-util/with-random-dump-dir clojure.core/let + metabase.actions.test-util/with-actions clojure.core/let + metabase.api.common/defroutes clojure.core/def + metabase.api.common/let-404 clojure.core/let + metabase.api.search-test/do-test-users clojure.core/let + metabase.async.api-response-test/with-response clojure.core/let + metabase.dashboard-subscription-test/with-dashboard-sub-for-card clojure.core/let + metabase.db.custom-migrations/define-migration clj-kondo.lint-as/def-catch-all + metabase.db.custom-migrations/define-reversible-migration clj-kondo.lint-as/def-catch-all + metabase.db.data-migrations/defmigration clojure.core/def + metabase.db.liquibase/with-liquibase clojure.core/let + metabase.db.schema-migrations-test.impl/with-temp-empty-app-db clojure.core/let + metabase.driver.mongo.connection/with-mongo-client clojure.core/let + metabase.driver.mongo.connection/with-mongo-database clojure.core/let + metabase.driver.mongo.query-processor/mongo-let clojure.core/let + metabase.driver.sql-jdbc.actions/with-jdbc-transaction clojure.core/let + metabase.driver.sql-jdbc.connection/with-connection-spec-for-testing-connection clojure.core/let + metabase.driver.sql-jdbc.execute.diagnostic/capturing-diagnostic-info clojure.core/fn + metabase.integrations.ldap/with-ldap-connection clojure.core/fn + metabase.models.collection-test/with-collection-in-location clojure.core/let + metabase.models.json-migration/def-json-migration clj-kondo.lint-as/def-catch-all + metabase.models.setting.multi-setting/define-multi-setting clojure.core/def + metabase.models.setting/defsetting clj-kondo.lint-as/def-catch-all + metabase.public-settings.premium-features/defenterprise-schema clj-kondo.lint-as/def-catch-all + metabase.public-settings.premium-features/define-premium-feature clojure.core/def + metabase.pulse-test/with-pulse-for-card clojure.core/let + metabase.query-processor-test.pipeline-queries-test/pmbql-query clojure.core/-> + metabase.query-processor-test.pipeline-queries-test/run-pmbql-query clojure.core/-> + metabase.query-processor.error-type/deferror clojure.core/def + metabase.query-processor.middleware.cache.impl/with-reducible-deserialized-results clojure.core/let + metabase.query-processor.middleware.process-userland-query-test/with-query-execution! clojure.core/let + metabase.query-processor.setup/with-qp-setup clojure.core/let + metabase.shared.util.namespaces/import-fns potemkin/import-vars + metabase.sync.util/sum-for clojure.core/for + metabase.sync.util/with-emoji-progress-bar clojure.core/let + metabase.test.data.interface/defdataset clojure.core/def + metabase.test.data.interface/defdataset-edn clojure.core/def + metabase.test/defdataset clojure.core/def + metabase.test/let-url clojure.core/let + metabase.test/with-actions clojure.core/let + metabase.test/with-grouper-batches! clojure.core/fn + metabase.test/with-open-channels clojure.core/let + metabase.test/with-single-admin-user clojure.core/fn + metabase.test/with-temp-dir clojure.core/let + metabase.test/with-temp-empty-app-db clojure.core/let + metabase.test/with-temp-file clojure.core/let + metabase.test/with-user-in-groups clojure.core/let + metabase.upload-test/with-upload-table! clojure.core/let + metabase.util.files/with-open-path-to-resource clojure.core/let + metabase.util.malli.defn/defn schema.core/defn + metabase.util.malli.defn/defn- schema.core/defn + metabase.util.malli.fn/fn schema.core/fn + metabase.util.malli/defmethod schema.core/defmethod + metabase.util.malli/defn schema.core/defn + metabase.util.malli/defn- schema.core/defn + metabase.util.malli/fn schema.core/fn + metabase.util.ssh/with-ssh-tunnel clojure.core/let + metabase.xrays.domain-entities.malli/defn schema.core/defn + monger.operators/defoperator clojure.core/def + potemkin.types/defprotocol+ clojure.core/defprotocol + potemkin.types/defrecord+ clojure.core/defrecord + potemkin.types/deftype+ clojure.core/deftype + potemkin/defprotocol+ clojure.core/defprotocol + potemkin/defrecord+ clojure.core/defrecord + potemkin/deftype+ clojure.core/deftype + toucan.db/with-call-counting clojure.core/fn + toucan2.core/with-call-count clojure.core/fn} :hooks {:analyze-call @@ -822,25 +822,25 @@ metabase-enterprise.audit-app.pages-test/with-temp-objects hooks.common/with-one-binding metabase-enterprise.cache.config-test/with-temp-persist-models hooks.common/with-seven-bindings metabase-enterprise.serialization.test-util/with-temp-dpc hooks.toucan2.tools.with-temp/with-temp - metabase.analytics.prometheus-test/with-prometheus-system hooks.common/with-two-bindings - metabase.api.alert-test/with-alert-in-collection hooks.common/with-four-bindings - metabase.api.automagic-dashboards-test/with-indexed-model hooks.metabase.api.automagic-dashboards-test/with-indexed-model + metabase.analytics.prometheus-test/with-prometheus-system! hooks.common/with-two-bindings + metabase.api.alert-test/with-alert-in-collection! hooks.common/with-four-bindings + metabase.api.automagic-dashboards-test/with-indexed-model! hooks.metabase.api.automagic-dashboards-test/with-indexed-model! metabase.api.card-test/with-card-param-values-fixtures hooks.common/let-one-with-optional-value - metabase.api.card-test/with-persistence-setup hooks.common/with-one-top-level-binding + metabase.api.card-test/with-persistence-setup! hooks.common/with-one-top-level-binding metabase.api.card-test/with-temp-native-card! hooks.common/with-two-bindings metabase.api.card-test/with-temp-native-card-with-params! hooks.common/with-two-bindings - metabase.api.collection-test/with-french-user-and-personal-collection hooks.common/with-two-top-level-bindings + metabase.api.collection-test/with-french-user-and-personal-collection! hooks.common/with-two-top-level-bindings metabase.api.common/defendpoint hooks.metabase.api.common/defendpoint metabase.api.common/defendpoint-async hooks.metabase.api.common/defendpoint metabase.api.common/defendpoint-async-schema hooks.metabase.api.common/defendpoint metabase.api.dashboard-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value metabase.api.dashboard-test/with-simple-dashboard-with-tabs hooks.common/with-one-binding metabase.api.embed-test/do-response-formats hooks.common/with-two-bindings - metabase.api.embed-test/with-chain-filter-fixtures hooks.common/let-one-with-optional-value + metabase.api.embed-test/with-chain-filter-fixtures! hooks.common/let-one-with-optional-value metabase.api.embed-test/with-temp-card hooks.common/let-one-with-optional-value metabase.api.embed-test/with-temp-dashcard hooks.common/let-one-with-optional-value - metabase.api.persist-test/with-setup hooks.common/with-one-top-level-binding - metabase.api.public-test/with-required-param-card hooks.common/with-one-binding + metabase.api.persist-test/with-setup! hooks.common/with-one-top-level-binding + metabase.api.public-test/with-required-param-card! hooks.common/with-one-binding metabase.api.public-test/with-temp-public-card hooks.common/let-one-with-optional-value metabase.api.public-test/with-temp-public-dashboard hooks.common/let-one-with-optional-value metabase.api.public-test/with-temp-public-dashboard-and-card hooks.common/with-three-bindings @@ -859,21 +859,21 @@ metabase.lib.test-util.macros/$ids hooks.metabase.test.data/$ids metabase.lib.test-util.macros/mbql-query hooks.metabase.test.data/mbql-query metabase.lib.test-util.macros/with-testing-against-standard-queries hooks.metabase.lib.test-util.macros/with-testing-against-standard-queries - metabase.models.collection-test/with-collection-hierarchy hooks.common/let-one-with-optional-value + metabase.models.collection-test/with-collection-hierarchy! hooks.common/let-one-with-optional-value metabase.models.collection-test/with-personal-and-impersonal-collections hooks.common/with-two-bindings metabase.models.dashboard-tab-test/with-dashtab-in-personal-collection hooks.common/with-one-top-level-binding - metabase.models.dashboard-test/with-dash-in-collection hooks.common/with-three-bindings + metabase.models.dashboard-test/with-dash-in-collection! hooks.common/with-three-bindings metabase.models.interface/define-batched-hydration-method hooks.metabase.models.interface/define-hydration-method metabase.models.interface/define-simple-hydration-method hooks.metabase.models.interface/define-hydration-method - metabase.models.pulse-test/with-dashboard-subscription-in-collection hooks.common/with-four-bindings - metabase.models.pulse-test/with-pulse-in-collection hooks.common/with-four-bindings + metabase.models.pulse-test/with-dashboard-subscription-in-collection! hooks.common/with-four-bindings + metabase.models.pulse-test/with-pulse-in-collection! hooks.common/with-four-bindings metabase.models.setting.multi-setting/define-multi-setting hooks.metabase.models.setting/define-multi-setting metabase.models.setting/defsetting hooks.metabase.models.setting/defsetting metabase.public-settings.premium-features/defenterprise hooks.metabase.public-settings.premium-features/defenterprise metabase.pulse.test-util/checkins-query-card hooks.metabase.test.data/$ids metabase.query-processor-test.expressions-test/calculate-bird-scarcity hooks.metabase.query-processor-test.expressions-test/calculate-bird-scarcity metabase.query-processor-test.filter-test/count-with-filter-clause hooks.metabase.test.data/$ids - metabase.query-processor.middleware.cache-test/with-mock-cache hooks.common/with-two-bindings + metabase.query-processor.middleware.cache-test/with-mock-cache! hooks.common/with-two-bindings metabase.sample-data-test/with-temp-sample-database-db hooks.common/with-one-binding metabase.stale-test/with-stale-items hooks.toucan2.tools.with-temp/with-temp metabase.test.data.users/with-group hooks.common/let-one-with-optional-value @@ -920,43 +920,43 @@ metabase.util/format-color hooks.metabase.util/format-color} :macroexpand - {clojurewerkz.quartzite.jobs/build macros.quartz/build-job - clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule - clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule - clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger - metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps! - metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps! - metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world - metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps! - metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps! - metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! macros.metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! - metabase-enterprise.query-reference-validation.api-test/with-test-setup macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup - metabase.api.card-test/with-ordered-items macros.metabase.api.card-test/with-ordered-items - metabase.api.collection-test/with-collection-hierarchy macros.metabase.api.collection-test/with-collection-hierarchy - metabase.api.collection-test/with-some-children-of-collection macros.metabase.api.collection-test/with-some-children-of-collection - metabase.api.common/define-routes macros.metabase.api.common/define-routes - metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing macros.metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing - metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing macros.metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing - metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing macros.metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing - metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing macros.metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing - metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter - metabase.lib.common/defop macros.metabase.lib.common/defop - metabase.models.params.chain-filter-test/chain-filter macros.metabase.models.params.chain-filter-test/chain-filter - metabase.models.params.chain-filter-test/chain-filter-search macros.metabase.models.params.chain-filter-test/chain-filter - metabase.models.query-analysis-test/with-test-setup macros.metabase.models.query-analysis-test/with-test-setup - metabase.models.user-test/with-groups macros.metabase.models.user-test/with-groups - metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response - metabase.related-test/with-world macros.metabase.related-test/with-world - metabase.shared.util.namespaces/import-fn macros.metabase.shared.util.namespaces/import-fn - metabase.task.setup.query-analysis-setup/with-test-setup! macros.metabase.task.setup.query-analysis-setup/with-test-setup! - metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user - metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value! - metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values - metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user - metabase.test/with-persistence-enabled macros.metabase.test.persistence/with-persistence-enabled - metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value! - metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values - metabase.xrays.domain-entities.malli/define-getters-and-setters macros.metabase.xrays.domain-entities.malli/define-getters-and-setters}} + {clojurewerkz.quartzite.jobs/build macros.quartz/build-job + clojurewerkz.quartzite.schedule.cron/schedule macros.quartz/schedule + clojurewerkz.quartzite.schedule.simple/schedule macros.quartz/simple-schedule + clojurewerkz.quartzite.triggers/build macros.quartz/build-trigger + metabase-enterprise.sandbox.test-util/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps! + metabase-enterprise.sandbox.test-util/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps! + metabase-enterprise.serialization.test-util/with-world macros.metabase-enterprise.serialization.test-util/with-world + metabase-enterprise.test/with-gtaps! macros.metabase-enterprise.sandbox.test-util/with-gtaps! + metabase-enterprise.test/with-gtaps-for-user! macros.metabase-enterprise.sandbox.test-util/with-gtaps! + metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! macros.metabase-enterprise.advanced-permissions.api.util-test/with-impersonations! + metabase-enterprise.query-reference-validation.api-test/with-test-setup! macros.metabase-enterprise.query-reference-validation.api-test/with-test-setup! + metabase.api.card-test/with-ordered-items macros.metabase.api.card-test/with-ordered-items + metabase.api.collection-test/with-collection-hierarchy! macros.metabase.api.collection-test/with-collection-hierarchy! + metabase.api.collection-test/with-some-children-of-collection! macros.metabase.api.collection-test/with-some-children-of-collection! + metabase.api.common/define-routes macros.metabase.api.common/define-routes + metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing! macros.metabase.api.embed-test/with-embedding-enabled-and-temp-card-referencing! + metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! macros.metabase.api.embed-test/with-embedding-enabled-and-temp-dashcard-referencing! + metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing! macros.metabase.api.public-test/with-sharing-enabled-and-temp-card-referencing! + metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing! macros.metabase.api.public-test/with-sharing-enabled-and-temp-dashcard-referencing! + metabase.lib.filter/deffilter macros.metabase.lib.filter/deffilter + metabase.lib.common/defop macros.metabase.lib.common/defop + metabase.models.params.chain-filter-test/chain-filter macros.metabase.models.params.chain-filter-test/chain-filter + metabase.models.params.chain-filter-test/chain-filter-search macros.metabase.models.params.chain-filter-test/chain-filter + metabase.models.query-analysis-test/with-test-setup macros.metabase.models.query-analysis-test/with-test-setup + metabase.models.user-test/with-groups macros.metabase.models.user-test/with-groups + metabase.query-processor.streaming/streaming-response macros.metabase.query-processor.streaming/streaming-response + metabase.related-test/with-world macros.metabase.related-test/with-world + metabase.shared.util.namespaces/import-fn macros.metabase.shared.util.namespaces/import-fn + metabase.task.setup.query-analysis-setup/with-test-setup! macros.metabase.task.setup.query-analysis-setup/with-test-setup! + metabase.test.data.users/with-group-for-user macros.metabase.test.data.users/with-group-for-user + metabase.test.util/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value! + metabase.test.util/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values + metabase.test/with-group-for-user macros.metabase.test.data.users/with-group-for-user + metabase.test/with-persistence-enabled! macros.metabase.test.persistence/with-persistence-enabled! + metabase.test/with-temp-env-var-value! macros.metabase.test.util/with-temp-env-var-value! + metabase.test/with-temporary-raw-setting-values macros.metabase.test.util/with-temporary-raw-setting-values + metabase.xrays.domain-entities.malli/define-getters-and-setters macros.metabase.xrays.domain-entities.malli/define-getters-and-setters}} :config-in-comment {:linters {:unresolved-symbol {:level :off}}} diff --git a/.clj-kondo/macros/metabase/api/collection_test.clj b/.clj-kondo/macros/metabase/api/collection_test.clj index 45f5bd29cf4e38357085839f2c1be870e98c85f3..27668bbb14c92a94d43277d4d86195c47f878934 100644 --- a/.clj-kondo/macros/metabase/api/collection_test.clj +++ b/.clj-kondo/macros/metabase/api/collection_test.clj @@ -1,7 +1,7 @@ (ns macros.metabase.api.collection-test (:require [macros.common])) -(defmacro with-collection-hierarchy [bindings & body] +(defmacro with-collection-hierarchy! [bindings & body] `(let [~(macros.common/ignore-unused 'a) nil ~(macros.common/ignore-unused 'b) nil ~(macros.common/ignore-unused 'c) nil @@ -12,6 +12,6 @@ ~bindings ~@body)) -(defmacro with-some-children-of-collection [collection-or-id-or-nil & body] +(defmacro with-some-children-of-collection! [collection-or-id-or-nil & body] `(let [~(macros.common/ignore-unused '&ids) ~collection-or-id-or-nil] ~@body)) diff --git a/.clj-kondo/macros/metabase/api/embed_test.clj b/.clj-kondo/macros/metabase/api/embed_test.clj index c49770b6c2fd13efa69771af406e928d354bab99..da7a0bc02fae73ccc9bb18f7882b67601ab5afaa 100644 --- a/.clj-kondo/macros/metabase/api/embed_test.clj +++ b/.clj-kondo/macros/metabase/api/embed_test.clj @@ -1,11 +1,11 @@ (ns macros.metabase.api.embed-test) -(defmacro with-embedding-enabled-and-temp-card-referencing +(defmacro with-embedding-enabled-and-temp-card-referencing! [table-kw field-kw [card-binding] & body] `(let [~card-binding [~table-kw ~field-kw]] ~@body)) -(defmacro with-embedding-enabled-and-temp-dashcard-referencing +(defmacro with-embedding-enabled-and-temp-dashcard-referencing! [table-kw field-kw [dash-binding card-binding dashcard-binding] & body] `(let [~dash-binding [~table-kw ~field-kw] ~(or card-binding '_) nil diff --git a/.clj-kondo/macros/metabase/api/public_test.clj b/.clj-kondo/macros/metabase/api/public_test.clj index 043f87bb44f4dcd9d096f2901e2703b636abae96..dddab913af14d5265452b836fd914678a2eb8ef1 100644 --- a/.clj-kondo/macros/metabase/api/public_test.clj +++ b/.clj-kondo/macros/metabase/api/public_test.clj @@ -1,11 +1,11 @@ (ns macros.metabase.api.public-test) -(defmacro with-sharing-enabled-and-temp-card-referencing +(defmacro with-sharing-enabled-and-temp-card-referencing! [table-kw field-kw [card-binding] & body] `(let [~card-binding [~table-kw ~field-kw]] ~@body)) -(defmacro with-sharing-enabled-and-temp-dashcard-referencing +(defmacro with-sharing-enabled-and-temp-dashcard-referencing! [table-kw field-kw [dash-binding card-binding dashcard-binding] & body] `(let [~dash-binding [~table-kw ~field-kw] ~(or card-binding '_) nil diff --git a/.clj-kondo/macros/metabase/test/persistence.clj b/.clj-kondo/macros/metabase/test/persistence.clj index 30d72cb0c0be115945862f15354166209df1d9f6..c240211776210d4f62af707ab27a9f7553a85819 100644 --- a/.clj-kondo/macros/metabase/test/persistence.clj +++ b/.clj-kondo/macros/metabase/test/persistence.clj @@ -1,5 +1,5 @@ (ns macros.metabase.test.persistence) -(defmacro with-persistence-enabled [[persist-fn-sym] & body] +(defmacro with-persistence-enabled! [[persist-fn-sym] & body] `(clojure.core/let [~persist-fn-sym (fn [])] ~@body)) diff --git a/.clj-kondo/macros/metabase_enterprise/query_reference_validation/api_test.clj b/.clj-kondo/macros/metabase_enterprise/query_reference_validation/api_test.clj index 893091db106389dcad437e4b36fe9e479f259d9f..8a706ed80f0389b198d814bd5c4e92927711ef1f 100644 --- a/.clj-kondo/macros/metabase_enterprise/query_reference_validation/api_test.clj +++ b/.clj-kondo/macros/metabase_enterprise/query_reference_validation/api_test.clj @@ -1,7 +1,7 @@ (ns macros.metabase-enterprise.query-reference-validation.api-test (:require [macros.common])) -(defmacro with-test-setup [& body] +(defmacro with-test-setup! [& body] `(let [~(macros.common/ignore-unused 'card-1) 1 ~(macros.common/ignore-unused 'card-2) 2 ~(macros.common/ignore-unused 'card-3) 3 diff --git a/.clj-kondo/src/hooks/clojure/core.clj b/.clj-kondo/src/hooks/clojure/core.clj index d8fe34f5fbc5d3a844700b33062e5a72927f34d4..37149622891f8a3a1c4f1b4c269d51c95d584445 100644 --- a/.clj-kondo/src/hooks/clojure/core.clj +++ b/.clj-kondo/src/hooks/clojure/core.clj @@ -141,7 +141,7 @@ (str/ends-with? s "!")) (defn- non-thread-safe-form-should-end-with-exclamation* - [{[defn-or-defmacro form-name] :children, :as node}] + [{[defn-or-defmacro form-name] :children, :as node} config] (when-not (and (:string-value form-name) (end-with-exclamation? (:string-value form-name))) (letfn [(walk [f form] @@ -151,9 +151,10 @@ (walk (fn [form] (when-let [qualified-symbol (hooks.common/node->qualified-symbol form)] (when (and (not (contains? symbols-allowed-in-fns-not-ending-in-an-exclamation-point qualified-symbol)) - (end-with-exclamation? qualified-symbol)) + (or (end-with-exclamation? qualified-symbol) + (contains? (get-in config [:linters :metabase/validate-deftest :parallel/unsafe]) qualified-symbol))) (hooks/reg-finding! (assoc (meta form-name) - :message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`." + :message (format "The name of this %s should end with `!` because it contains calls to non thread safe form `%s`. [:metabase/test-helpers-use-non-thread-safe-functions]" (:string-value defn-or-defmacro) qualified-symbol) :type :metabase/test-helpers-use-non-thread-safe-functions))))) node)) @@ -164,10 +165,10 @@ A function or a macro can be defined as 'not thread safe' when their funciton name ends with a `!`. Only used in tests to identify thread-safe/non-thread-safe test helpers. See #37126" - [{:keys [node cljc lang]}] + [{:keys [node cljc lang config]}] (when (or (not cljc) (= lang :clj)) - (non-thread-safe-form-should-end-with-exclamation* node)) + (non-thread-safe-form-should-end-with-exclamation* node config)) {:node node}) (comment diff --git a/.clj-kondo/src/hooks/clojure/test.clj b/.clj-kondo/src/hooks/clojure/test.clj index 8585dd8caf7525a1d1b1693b8fef9a03e05671c2..2cc51e1bba2e1617f4e3e63b48fbccd05f0e38d0 100644 --- a/.clj-kondo/src/hooks/clojure/test.clj +++ b/.clj-kondo/src/hooks/clojure/test.clj @@ -12,10 +12,10 @@ (f [form] (when-let [qualified-symbol (hooks.common/node->qualified-symbol form)] (cond - (contains? (:disallowed config) qualified-symbol) + (contains? (:parallel/unsafe config) qualified-symbol) (error! form (format "%s is not allowed inside a ^:parallel test or test fixture [:metabase/validate-deftest]" qualified-symbol)) - (and (not (contains? (:allowed config) qualified-symbol)) + (and (not (contains? (:parallel/safe config) qualified-symbol)) (str/ends-with? (name qualified-symbol) "!")) (error! form (format "destructive functions like %s are not allowed inside a ^:parallel test or test fixture. If this should be allowed, add it to the whitelist in the Kondo config file [:metabase/validate-deftest]" qualified-symbol))))) diff --git a/.clj-kondo/src/hooks/metabase/api/automagic_dashboards_test.clj b/.clj-kondo/src/hooks/metabase/api/automagic_dashboards_test.clj index 5fe321c6937e3de26ec6fc757710ff1962f81cd8..262db63225f7552643bc8ef37d19dda0b824dc11 100644 --- a/.clj-kondo/src/hooks/metabase/api/automagic_dashboards_test.clj +++ b/.clj-kondo/src/hooks/metabase/api/automagic_dashboards_test.clj @@ -4,8 +4,8 @@ (comment (def input (hooks/parse-string - "(with-indexed-model [{:keys [model model-index model-index-value]} - {:query :some-query}] + "(with-indexed-model! [{:keys [model model-index model-index-value]} + {:query :some-query}] :foo :bar :body)")) (println (hooks/sexpr input)) @@ -13,12 +13,12 @@ (let [f (load-file ".clj-kondo/hooks/metabase/api/automagic_dashboards_test.clj")] (f {:node input}))) -(defn with-indexed-model +(defn with-indexed-model! [{node :node}] (let [[_macro-call binding-and-query-info & body] (:children node) [binding query-info] (:children binding-and-query-info) node* (hooks/list-node - [(hooks/token-node 'do-with-testing-model) + [(hooks/token-node 'do-with-indexed-model!) query-info (hooks/list-node (list* (hooks/token-node 'fn) diff --git a/.clj-kondo/src/hooks/metabase/api/common.clj b/.clj-kondo/src/hooks/metabase/api/common.clj index 83e46a7c3fc6d8a8c2a74292fc6babc6f0902926..622ff3c8c2d66d474914741a4409e00c671f8334 100644 --- a/.clj-kondo/src/hooks/metabase/api/common.clj +++ b/.clj-kondo/src/hooks/metabase/api/common.clj @@ -15,16 +15,17 @@ [arg] (letfn [(update-defendpoint [node] (let [[_defendpoint method route & body] (:children node)] - (api/list-node - (list - (api/token-node 'do) - (api/token-node (symbol "compojure.core" (str (api/sexpr method)))) - (-> (api/list-node - (list* - (api/token-node 'clojure.core/defn) - (api/token-node (route-fn-name (api/sexpr method) (api/sexpr route))) - body)) - (with-meta (meta node)))))))] + (-> (api/list-node + (list + (api/token-node 'do) + (api/token-node (symbol "compojure.core" (str (api/sexpr method)))) + (-> (api/list-node + (list* + (api/token-node 'clojure.core/defn) + (api/token-node (route-fn-name (api/sexpr method) (api/sexpr route))) + body)) + (with-meta (meta node))))) + (with-meta {:clj-kondo/ignore [:clojure-lsp/unused-public-var]}))))] (update arg :node update-defendpoint))) (comment diff --git a/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/util_test.clj b/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/util_test.clj index 778439a56f476f712c34afcbf7d50bee38d4b5dc..44bfd8ab16f2e82b9acd719475175aadc491f724 100644 --- a/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/util_test.clj +++ b/enterprise/backend/test/metabase_enterprise/advanced_permissions/api/util_test.clj @@ -35,7 +35,7 @@ (data-perms/set-database-permission! group (data/id) :perms/create-queries :query-builder-and-native) (let [{:keys [impersonations attributes]} args] ;; set user login_attributes - (met/with-user-attributes test-user-name-or-user-id attributes + (met/with-user-attributes! test-user-name-or-user-id attributes (do-with-conn-impersonation-defs group impersonations (fn [] ;; bind user as current user, then run f diff --git a/enterprise/backend/test/metabase_enterprise/airgap_test.clj b/enterprise/backend/test/metabase_enterprise/airgap_test.clj index 58db1cede52a361a08f87e2babe3e3160ab2bbf8..0f8ba9c33a59f2d68c7adaea79cdba185181325f 100644 --- a/enterprise/backend/test/metabase_enterprise/airgap_test.clj +++ b/enterprise/backend/test/metabase_enterprise/airgap_test.clj @@ -5,7 +5,7 @@ [metabase-enterprise.airgap :as airgap] [metabase.public-settings.premium-features :as premium-features])) -(defn- test-fake-features [& {:keys [token-fn pubk-fn]}] +(defn- test-fake-features! [& {:keys [token-fn pubk-fn]}] (with-redefs ;; due to the way premium embedding token is implemented, we need to provide a token and a public key, ;; so we cannot set this with `mt/with-temporary-setting-values`. @@ -19,26 +19,26 @@ (deftest ag-token-decryption-test ;; Checks for a specific feature encoded into the fake token: - (is (contains? (set (map hash (:features (test-fake-features)))) 2117420126))) + (is (contains? (set (map hash (:features (test-fake-features!)))) 2117420126))) (deftest ag-token-valid-now-test (testing "Token time is valid" ;; the test token is valid until 2054 - (is (true? (#'airgap/valid-now? (test-fake-features)))))) + (is (true? (#'airgap/valid-now? (test-fake-features!)))))) (deftest ag-invalid-token-test (testing "Invalid token format" (is (thrown-with-msg? Exception #"Message seems corrupt or manipulated." - (test-fake-features :token-fn (fn [token] (str token "x")))))) + (test-fake-features! :token-fn (fn [token] (str token "x")))))) (testing "Invalid public key format" (is (thrown-with-msg? Exception #"unable to convert key pair: encoded key spec not recognized: invalid info structure in RSA public key" - (test-fake-features + (test-fake-features! :pubk-fn (fn [_] (io/reader (io/resource "broken_pubkey.pem")))))))) (deftest ag-missing-pubkey-test (testing "Missing public key" (is (thrown-with-msg? Exception #"No public key available for airgap token" - (test-fake-features :pubk-fn (fn [_] nil)))))) + (test-fake-features! :pubk-fn (fn [_] nil)))))) diff --git a/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj index 9f9da6588e5d22ec5c4bd4b690b2df017c72d8fa..b284264c65ef9e2efe9df8a15f805dcc7f96b7c5 100644 --- a/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj +++ b/enterprise/backend/test/metabase_enterprise/enhancements/integrations/ldap_test.clj @@ -13,7 +13,7 @@ (deftest find-test (mt/with-premium-features #{:sso-ldap} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "find by username" (is (= {:dn "cn=John Smith,ou=People,dc=metabase,dc=com" :first-name "John" @@ -93,7 +93,7 @@ (deftest attribute-sync-test (mt/with-premium-features #{:sso-ldap} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "find by email/username should return other attributes as well" (is (= {:dn "cn=Lucky Pigeon,ou=Birds,dc=metabase,dc=com" :first-name "Lucky" @@ -166,7 +166,7 @@ (deftest update-attributes-on-login-test (mt/with-premium-features #{:sso-ldap} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "Existing user's attributes are updated on fetch" (try (let [user-info (ldap/find-user "jsmith1")] @@ -216,7 +216,7 @@ (deftest fetch-or-create-user-test (mt/with-premium-features #{:sso-ldap} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "a new user is created when they don't already exist" (try (ldap/fetch-or-create-user! (ldap/find-user "jsmith1")) @@ -252,7 +252,7 @@ (deftest ldap-no-user-provisioning-test (mt/with-premium-features #{:sso-ldap} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "an error is thrown when a new user is fetched and user provisioning is not enabled" (with-redefs [sso-settings/ldap-user-provisioning-enabled? (constantly false) public-settings/site-name (constantly "test")] diff --git a/enterprise/backend/test/metabase_enterprise/query_reference_validation/api_test.clj b/enterprise/backend/test/metabase_enterprise/query_reference_validation/api_test.clj index ef7e5262a46e35416ea23b90681065fa0054aeec..a88e5f4770e5ad531a48bd982065c6e5991b6a9e 100644 --- a/enterprise/backend/test/metabase_enterprise/query_reference_validation/api_test.clj +++ b/enterprise/backend/test/metabase_enterprise/query_reference_validation/api_test.clj @@ -8,7 +8,7 @@ [toucan2.core :as t2] [toucan2.tools.with-temp :as t2.with-temp])) -(defn- do-with-test-setup [f] +(defn- do-with-test-setup! [f] (query-analysis/without-analysis (t2.with-temp/with-temp [:model/Table {table-1 :id} {:name "T1"} :model/Table {table-2 :id} {:name "T2" :active false} @@ -117,14 +117,14 @@ (mt/with-temporary-setting-values [query-analysis-enabled true] (mt/call-with-map-params f [card-1 card-2 card-3 card-4 card-5 coll-2 coll-3])))))) -(defmacro ^:private with-test-setup +(defmacro ^:private with-test-setup! "Creates some non-stale QueryFields and anaphorically provides stale QueryField IDs called `qf-{1-3}` and `qf-1b` and their corresponding Card IDs (`card-{1-3}`). The cards are named A, B, and C. The Fields are called FA, FB, FB and they all point to a Table called T. Both `qf-1` and `qf-1b` refer to `card-1`. `card-4` is guaranteed not to have problems" [& body] - `(do-with-test-setup + `(do-with-test-setup! (mt/with-anaphora [card-1 card-2 card-3 card-4 card-5 coll-2 coll-3] ~@body))) @@ -137,7 +137,7 @@ (deftest collection-ancestors-test (testing "The response includes collection ancestors" - (with-test-setup + (with-test-setup! (is (= [{:collection {:id nil :name nil :authority_level nil @@ -175,14 +175,14 @@ (deftest setting-test (testing "It requires the query analysis setting" - (with-test-setup + (with-test-setup! (mt/with-temporary-setting-values [query-analysis-enabled false] (is (= "Query Analysis must be enabled to use the Query Reference Validator" (mt/user-http-request :crowberto :get 429 url))))))) (deftest list-invalid-cards-basic-test (testing "Only returns cards with problematic field refs" - (with-test-setup + (with-test-setup! (is (= {:total 4 :data [{:id card-1 @@ -204,7 +204,7 @@ (deftest pagination-test (testing "Lets you page results" - (with-test-setup + (with-test-setup! (is (= {:total 4 :limit 2 :offset 0 @@ -235,7 +235,7 @@ (deftest sorting-test (testing "Lets you specify the sort key" - (with-test-setup + (with-test-setup! (is (= {:total 4 :data [{:id card-3} @@ -265,7 +265,7 @@ (with-data-keys [:id])))))) (testing "Rejects bad keys" - (with-test-setup + (with-test-setup! (is (str/starts-with? (:sort_column (:errors (mt/user-http-request :crowberto :get 400 (str url "?sort_column=favorite_bird")))) @@ -273,7 +273,7 @@ (deftest filter-on-collection (testing "can filter on collection id" - (with-test-setup + (with-test-setup! (testing "we can just look in coll-3" (is (= {:total 1 :data diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj index 7c9812f2c5cac50b5313e162004c18b57895333f..6a8a178671a211b8486c08cc8778459ba55d3066 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/api/gtap_test.clj @@ -28,7 +28,7 @@ :group_id true :attribute_remappings {:foo 1}}) -(defmacro ^:private with-gtap-cleanup +(defmacro ^:private with-gtap-cleanup! "Invokes `body` ensuring any `GroupTableAccessPolicy` created will be removed afterward. Leaving behind a GTAP can case referential integrity failures for any related `Card` that would be cleaned up as part of a `with-temp*` call" [& body] @@ -58,7 +58,7 @@ (deftest fetch-gtap-test (testing "GET /api/mt/gtap/" - (with-gtap-cleanup + (with-gtap-cleanup! (mt/with-temp [Table {table-id-1 :id} {} Table {table-id-2 :id} {} PermissionsGroup {group-id-1 :id} {} @@ -90,7 +90,7 @@ PermissionsGroup {group-id :id} {}] (testing "Test that we can create a new GTAP" (t2.with-temp/with-temp [Card {card-id :id}] - (with-gtap-cleanup + (with-gtap-cleanup! (let [post-results (gtap-post {:table_id table-id :group_id group-id :card_id card-id @@ -101,7 +101,7 @@ (mt/user-http-request :crowberto :get 200 (format "mt/gtap/%s" (:id post-results))))))))) (testing "Test that we can create a new GTAP without a card" - (with-gtap-cleanup + (with-gtap-cleanup! (let [post-results (gtap-post {:table_id table-id :group_id group-id :card_id nil @@ -116,7 +116,7 @@ Card {card-id :id} {:dataset_query (mt/mbql-query venues {:fields [[:expression "My field"]] :expressions {"My field" [:ltrim "wow"]}})}] - (with-gtap-cleanup + (with-gtap-cleanup! (is (=? {:message "Sandbox Questions can't return columns that have different types than the Table they are sandboxing." :expected "type/Integer" :actual "type/Text"} @@ -132,14 +132,14 @@ PermissionsGroup {group-id :id} {}] (testing "A valid sandbox passes validation and returns no error" (t2.with-temp/with-temp [Card {card-id :id}] - (with-gtap-cleanup + (with-gtap-cleanup! (mt/user-http-request :crowberto :post 204 "mt/gtap/validate" {:table_id table-id :group_id group-id :card_id card-id})))) (testing "A sandbox without a card-id passes validation, because the validation is not applicable in this case" - (with-gtap-cleanup + (with-gtap-cleanup! (mt/user-http-request :crowberto :post 204 "mt/gtap/validate" {:table_id table-id :group_id group-id @@ -151,7 +151,7 @@ Card {card-id :id} {:dataset_query (mt/mbql-query venues {:fields [[:expression "My field"]] :expressions {"My field" [:ltrim "wow"]}})}] - (with-gtap-cleanup + (with-gtap-cleanup! (is (=? {:message "Sandbox Questions can't return columns that have different types than the Table they are sandboxing." :expected "type/Integer" :actual "type/Text"} @@ -167,7 +167,7 @@ (mt/with-temp [Table {table-id :id} {} PermissionsGroup {group-id :id} {} Card {card-id :id} {}] - (with-gtap-cleanup + (with-gtap-cleanup! (let [{:keys [id]} (gtap-post {:table_id table-id :group_id group-id :card_id card-id @@ -234,7 +234,7 @@ Card {card-id-1 :id} {} Card {card-id-2 :id} {}] (mt/with-premium-features #{:sandboxes} - (with-gtap-cleanup + (with-gtap-cleanup! (testing "Test that we can create a new sandbox using the permission graph API" (let [graph (-> (data-perms.graph/api-graph) (assoc-in [:groups group-id (mt/id) :view-data] {"PUBLIC" {table-id-1 :sandboxed}}) diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj index c3c17b3117e3ca22c2af0fcdba6441ca87f71773..02790cce8289f25dbd0191557fc4c2659916b61b 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/pulse_test.clj @@ -34,7 +34,7 @@ (is (= [[10]] (send-pulse-created-by-user! :rasta)))))) -(defn- alert-results +(defn- alert-results! "Results for creating and running an Alert" [query] (mt/with-temp [Card pulse-card {:name "Test card" @@ -122,7 +122,7 @@ (testing "GTAPs should apply to Pulses — they should get the same results as if running that query normally" (is (= [[3 13]] (mt/rows - (alert-results query))))))))) + (alert-results! query))))))))) (defn- html->row-count [html] (or (some->> html (re-find #"of <strong.+>(\d+)</strong> rows") second Integer/parseUnsignedInt) @@ -151,7 +151,7 @@ (testing "Pulse should be sandboxed" (is (= 22 - (count (mt/rows (alert-results query)))))))))))) + (count (mt/rows (alert-results! query)))))))))))) (deftest pulse-preview-test (testing "Pulse preview endpoints should be sandboxed" diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj index 3ce6b1e8d6cbd049c4db5e9dc2ddd51e553f8c1c..aed199784eac671e99862065d2bf72b3d6b477cb 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/query_processor/middleware/row_level_restrictions_test.clj @@ -470,7 +470,7 @@ :order-by [[:asc $venue_id->venues.price]] :breakout [$venue_id->venues.price $user_id->users.name]})))))))))) -(defn- run-query-returning-remark [run-query-fn] +(defn- run-query-returning-remark! [run-query-fn] (let [remark (atom nil) orig qp.util/query->remark] (with-redefs [qp.util/query->remark (fn [driver outer-query] @@ -486,7 +486,7 @@ (met/with-gtaps! {:gtaps {:venues (venues-category-mbql-gtap-def)} :attributes {"cat" 50}} (is (= (format "Metabase:: userID: %d queryType: MBQL queryHash: <hash>" (mt/user->id :rasta)) - (run-query-returning-remark + (run-query-returning-remark! (fn [] (mt/user-http-request :rasta :post "dataset" (mt/mbql-query venues {:aggregation [[:count]]}))))))))) @@ -735,7 +735,7 @@ (mt/rows (run-query))))))))))) (deftest dont-cache-sandboxes-test - (cache-test/with-mock-cache [save-chan] + (cache-test/with-mock-cache! [save-chan] (met/with-gtaps! {:gtaps {:venues (venues-category-mbql-gtap-def)} :attributes {"cat" 50}} (letfn [(run-query [] @@ -762,7 +762,7 @@ (is (= [[10]] (mt/rows result))))) (testing "Run the query with different User attributes, should not get the cached result" - (met/with-user-attributes :rasta {"cat" 40} + (met/with-user-attributes! :rasta {"cat" 40} ;; re-bind current user so updated attributes come in to effect (mt/with-test-user :rasta (is (= {"cat" 40} @@ -1083,7 +1083,7 @@ ["dimension" [:field (mt/id :products :category) nil]]}}}} - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (mt/with-temp [Card model {:type :model :dataset_query (mt/mbql-query products @@ -1106,7 +1106,7 @@ :database (mt/id)} regular-result (mt/with-test-user :crowberto (qp/process-query query)) - sandboxed-result (met/with-user-attributes :rasta {"category" "Gizmo"} + sandboxed-result (met/with-user-attributes! :rasta {"category" "Gizmo"} (mt/with-test-user :rasta (qp/process-query query)))] (testing "Unsandboxed" @@ -1133,7 +1133,7 @@ :table_id (mt/id :categories) :dataset_query (mt/mbql-query categories)}] (let [query (:dataset_query card)] - (process-userland-query-test/with-query-execution [qe query] + (process-userland-query-test/with-query-execution! [qe query] (qp/process-query (qp/userland-query query)) (is (=? {:is_sandboxed true} (qe))))))))) diff --git a/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj index a5f50eeca2a7fbcef7e39de4c7fa1b620788fb76..f1ff5f4617999c083de10c1af67f41eb8df24578 100644 --- a/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj +++ b/enterprise/backend/test/metabase_enterprise/sandbox/test_util.clj @@ -17,20 +17,20 @@ [metabase.util :as u] [toucan2.tools.with-temp :as t2.with-temp])) -(defn do-with-user-attributes [test-user-name-or-user-id attributes-map thunk] - (mb.hawk.parallel/assert-test-is-not-parallel "with-user-attributes") +(defn do-with-user-attributes! [test-user-name-or-user-id attributes-map thunk] + (mb.hawk.parallel/assert-test-is-not-parallel "with-user-attributes!") (let [user-id (test.users/test-user-name-or-user-id->user-id test-user-name-or-user-id)] (tu/with-temp-vals-in-db User user-id {:login_attributes attributes-map} (thunk)))) -(defmacro with-user-attributes +(defmacro with-user-attributes! "Execute `body` with the attributes for a User temporarily set to `attributes-map`. `test-user-name-or-user-id` can be either one of the predefined test users e.g. `:rasta` or a User ID. - (with-user-attributes :rasta {\"cans\" 2} ...)" + (with-user-attributes! :rasta {\"cans\" 2} ...)" {:style/indent 2} [test-user-name-or-user-id attributes-map & body] - `(do-with-user-attributes ~test-user-name-or-user-id ~attributes-map (fn [] ~@body))) + `(do-with-user-attributes! ~test-user-name-or-user-id ~attributes-map (fn [] ~@body))) (defn- do-with-gtap-defs! {:style/indent 2} @@ -69,7 +69,7 @@ (test.users/with-group-for-user [group test-user-name-or-user-id] (let [{:keys [gtaps attributes]} (mc/assert WithGTAPsArgs (args-fn))] ;; set user login_attributes - (with-user-attributes test-user-name-or-user-id attributes + (with-user-attributes! test-user-name-or-user-id attributes (mt/with-additional-premium-features #{:sandboxes} ;; create Cards/GTAPs from defs (do-with-gtap-defs! group gtaps diff --git a/enterprise/backend/test/metabase_enterprise/snippet_collections/models/native_query_snippet/permissions_test.clj b/enterprise/backend/test/metabase_enterprise/snippet_collections/models/native_query_snippet/permissions_test.clj index a75f16e5c99bcf4c6bd5b0d146bdba294ff284d7..61ee99ffc1cb9dc257cc3ca1524ef6ff0c250eba 100644 --- a/enterprise/backend/test/metabase_enterprise/snippet_collections/models/native_query_snippet/permissions_test.clj +++ b/enterprise/backend/test/metabase_enterprise/snippet_collections/models/native_query_snippet/permissions_test.clj @@ -50,7 +50,7 @@ (testing "should NOT be allowed if you do not have native query perms for at least one DB" (test-perms* false))))))) -(defn- test-with-root-collection-and-collection [f] +(defn- test-with-root-collection-and-collection! [f] (mt/with-non-admin-groups-no-root-collection-for-namespace-perms "snippets" (t2.with-temp/with-temp [Collection collection {:name "Parent Collection", :namespace "snippets"}] (doseq [coll [root-collection collection]] @@ -60,7 +60,7 @@ (deftest read-perms-test (testing "read a Snippet" - (test-with-root-collection-and-collection + (test-with-root-collection-and-collection! (fn [coll snippet] (test-perms! :has-perms-for-obj? #(mi/can-read? snippet) @@ -70,7 +70,7 @@ (deftest create-perms-test (testing "create a Snippet" - (test-with-root-collection-and-collection + (test-with-root-collection-and-collection! (fn [coll snippet] (test-perms! :has-perms-for-obj? #(mi/can-create? NativeQuerySnippet (dissoc snippet :id)) @@ -79,7 +79,7 @@ (deftest update-perms-test (testing "update a Snippet" - (test-with-root-collection-and-collection + (test-with-root-collection-and-collection! (fn [coll snippet] (test-perms! :has-perms-for-obj? #(mi/can-write? snippet) diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj index 6aca3e057b2634865a1bca3a7c40eb2a9ce0efd1..ed33b5b84a783445979250cf3126543dbb06cb22 100644 --- a/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/jwt_test.clj @@ -24,13 +24,6 @@ (use-fixtures :once (fixtures/initialize :test-users)) -(defn- disable-other-sso-types [thunk] - (mt/with-temporary-setting-values [ldap-enabled false - saml-enabled false] - (thunk))) - -(use-fixtures :each disable-other-sso-types) - (defn- disable-api-url-prefix [thunk] (binding [client/*url-prefix* ""] @@ -38,11 +31,16 @@ (use-fixtures :each disable-api-url-prefix) +(defn- do-with-other-sso-types-disabled! [thunk] + (mt/with-temporary-setting-values [ldap-enabled false + saml-enabled false] + (thunk))) + (def ^:private default-idp-uri "http://test.idp.metabase.com") (def ^:private default-redirect-uri "/") (def ^:private default-jwt-secret (crypto-random/hex 32)) -(defn- call-with-default-jwt-config [f] +(defn- call-with-default-jwt-config! [f] (let [current-features (premium-features/*token-features*)] (mt/with-additional-premium-features #{:sso-jwt} (mt/with-temporary-setting-values [jwt-enabled true @@ -52,61 +50,63 @@ (mt/with-premium-features current-features (f)))))) -(defmacro with-default-jwt-config [& body] - `(call-with-default-jwt-config +(defmacro with-default-jwt-config! [& body] + `(call-with-default-jwt-config! (fn [] ~@body))) -(defmacro ^:private with-jwt-default-setup [& body] +(defmacro ^:private with-jwt-default-setup! [& body] `(mt/test-helpers-set-global-values! (mt/with-premium-features #{:audit-app} - (disable-other-sso-types + (do-with-other-sso-types-disabled! (fn [] (mt/with-additional-premium-features #{:sso-jwt} (saml-test/call-with-login-attributes-cleared! (fn [] - (call-with-default-jwt-config + (call-with-default-jwt-config! (fn [] ~@body)))))))))) (deftest sso-prereqs-test - (mt/with-additional-premium-features #{:sso-jwt} - (testing "SSO requests fail if JWT hasn't been configured or enabled" - (mt/with-temporary-setting-values [jwt-enabled false - jwt-identity-provider-uri nil - jwt-shared-secret nil] - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso"))) + (do-with-other-sso-types-disabled! + (fn [] + (mt/with-additional-premium-features #{:sso-jwt} + (testing "SSO requests fail if JWT hasn't been configured or enabled" + (mt/with-temporary-setting-values [jwt-enabled false + jwt-identity-provider-uri nil + jwt-shared-secret nil] + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso"))) - (testing "SSO requests fail if they don't have a valid premium-features token" - (with-default-jwt-config - (mt/with-premium-features #{} - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso")))))))) + (testing "SSO requests fail if they don't have a valid premium-features token" + (with-default-jwt-config! + (mt/with-premium-features #{} + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso")))))))) - (testing "SSO requests fail if JWT is enabled but hasn't been configured" - (mt/with-temporary-setting-values [jwt-enabled true - jwt-identity-provider-uri nil] - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso"))))) + (testing "SSO requests fail if JWT is enabled but hasn't been configured" + (mt/with-temporary-setting-values [jwt-enabled true + jwt-identity-provider-uri nil] + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso"))))) - (testing "SSO requests fail if JWT is configured but hasn't been enabled" - (mt/with-temporary-setting-values [jwt-enabled false - jwt-identity-provider-uri default-idp-uri - jwt-shared-secret default-jwt-secret] - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso"))))) + (testing "SSO requests fail if JWT is configured but hasn't been enabled" + (mt/with-temporary-setting-values [jwt-enabled false + jwt-identity-provider-uri default-idp-uri + jwt-shared-secret default-jwt-secret] + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso"))))) - (testing "The JWT Shared Secret must also be included for SSO to be configured" - (mt/with-temporary-setting-values [jwt-enabled true - jwt-identity-provider-uri default-idp-uri - jwt-shared-secret nil] - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso"))))))) + (testing "The JWT Shared Secret must also be included for SSO to be configured" + (mt/with-temporary-setting-values [jwt-enabled true + jwt-identity-provider-uri default-idp-uri + jwt-shared-secret nil] + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso"))))))))) (deftest redirect-test (testing "with JWT configured, a GET request should result in a redirect to the IdP" - (with-jwt-default-setup + (with-jwt-default-setup! (let [result (client/client-full-response :get 302 "/auth/sso" {:request-options {:redirect-strategy :none}} :redirect default-redirect-uri) @@ -114,7 +114,7 @@ (is (str/starts-with? redirect-url default-idp-uri))))) (testing (str "JWT configured with a redirect-uri containing query params, " "a GET request should result in a redirect to the IdP as a correctly formatted URL (#13078)") - (with-jwt-default-setup + (with-jwt-default-setup! (mt/with-temporary-setting-values [jwt-identity-provider-uri "http://test.idp.metabase.com/login?some_param=yes"] (let [result (client/client-full-response :get 302 "/auth/sso" {:request-options {:redirect-strategy :none}} @@ -123,25 +123,26 @@ (is (str/includes? redirect-url "&return_to="))))))) (deftest jwt-saml-both-enabled-test - (with-jwt-default-setup - (saml-test/with-saml-default-setup - (testing "with SAML and JWT configured, a GET request with JWT params should sign in correctly" - (let [response (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :return_to default-redirect-uri - :jwt (jwt/sign {:email "rasta@metabase.com" - :first_name "Rasta" - :last_name "Toucan" - :extra "keypairs" - :are "also present"} - default-jwt-secret))] - (is (saml-test/successful-login? response)) - (testing "redirect URI" - (is (= default-redirect-uri - (get-in response [:headers "Location"])))) - (testing "login attributes" - (is (= {"extra" "keypairs", "are" "also present"} - (t2/select-one-fn :login_attributes User :email "rasta@metabase.com")))))) + (with-jwt-default-setup! + (saml-test/with-saml-default-setup! + (mt/with-temporary-setting-values [jwt-enabled true] + (testing "with SAML and JWT configured, a GET request with JWT params should sign in correctly" + (let [response (client/client-real-response :get 302 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :return_to default-redirect-uri + :jwt (jwt/sign {:email "rasta@metabase.com" + :first_name "Rasta" + :last_name "Toucan" + :extra "keypairs" + :are "also present"} + default-jwt-secret))] + (is (saml-test/successful-login? response)) + (testing "redirect URI" + (is (= default-redirect-uri + (get-in response [:headers "Location"])))) + (testing "login attributes" + (is (= {"extra" "keypairs", "are" "also present"} + (t2/select-one-fn :login_attributes User :email "rasta@metabase.com"))))))) (testing "with SAML and JWT configured, a GET request without JWT params should redirect to SAML IdP" (let [response (client/client-full-response :get 302 "/auth/sso" @@ -152,7 +153,7 @@ (deftest happy-path-test (testing (str "Happy path login, valid JWT, checks to ensure the user was logged in successfully and the redirect to " "the right location") - (with-jwt-default-setup + (with-jwt-default-setup! (let [response (client/client-real-response :get 302 "/auth/sso" {:request-options {:redirect-strategy :none}} :return_to default-redirect-uri :jwt (jwt/sign {:email "rasta@metabase.com" @@ -175,7 +176,7 @@ (deftest no-open-redirect-test (testing "Check that we prevent open redirects to untrusted sites" - (with-jwt-default-setup + (with-jwt-default-setup! (doseq [redirect-uri ["https://badsite.com" "//badsite.com" "https:///badsite.com"]] @@ -193,7 +194,7 @@ (deftest expired-jwt-test (testing "Check an expired JWT" - (with-jwt-default-setup + (with-jwt-default-setup! (is (= "Token is older than max-age (180)" (:message (client/client :get 401 "/auth/sso" {:request-options {:redirect-strategy :none}} :return_to default-redirect-uri @@ -209,7 +210,7 @@ (deftest create-new-account-test (testing "A new account will be created for a JWT user we haven't seen before" - (with-jwt-default-setup + (with-jwt-default-setup! (with-users-with-email-deleted "newuser@metabase.com" (letfn [(new-user-exists? [] (boolean (seq (t2/select User :%lower.email "newuser@metabase.com"))))] @@ -248,7 +249,7 @@ (deftest update-account-test (testing "A new account with 'Unknown' name will be created for a new JWT user without a first or last name." - (with-jwt-default-setup + (with-jwt-default-setup! (with-users-with-email-deleted "newuser@metabase.com" (letfn [(new-user-exists? [] (boolean (seq (t2/select User :%lower.email "newuser@metabase.com"))))] @@ -310,7 +311,7 @@ (deftest login-sync-group-memberships-test (testing "login should sync group memberships if enabled" - (with-jwt-default-setup + (with-jwt-default-setup! (mt/with-temp [PermissionsGroup my-group {:name (str ::my-group)}] (mt/with-temporary-setting-values [jwt-group-sync true jwt-group-mappings {"my_group" [(u/the-id my-group)]} @@ -333,7 +334,7 @@ (deftest create-new-jwt-user-no-user-provisioning-test (testing "When user provisioning is disabled, throw an error if we attempt to create a new user." - (with-jwt-default-setup + (with-jwt-default-setup! (with-redefs [sso-settings/jwt-user-provisioning-enabled? (constantly false) public-settings/site-name (constantly "test")] (is @@ -344,7 +345,7 @@ (deftest jwt-token-test (testing "should return a session token when token=true" - (with-jwt-default-setup + (with-jwt-default-setup! (mt/with-temporary-setting-values [enable-embedding true] (let [jwt-iat-time (buddy-util/now) jwt-exp-time (+ (buddy-util/now) 3600) @@ -365,7 +366,7 @@ (:body result))))))) (testing "should not return a session token when embedding is disabled" - (with-jwt-default-setup + (with-jwt-default-setup! (mt/with-temporary-setting-values [enable-embedding false] (let [jwt-iat-time (buddy-util/now) jwt-exp-time (+ (buddy-util/now) 3600) @@ -383,7 +384,7 @@ (is result nil))))) (testing "should not return a session token when token=false" - (with-jwt-default-setup + (with-jwt-default-setup! (mt/with-temporary-setting-values [enable-embedding true] (let [jwt-iat-time (buddy-util/now) jwt-exp-time (+ (buddy-util/now) 3600) diff --git a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj index a83fe848f6834060956c2f1bf7c009730f24ffc4..f42842f2d0b8764b1c5f5d26077435c7a46351cd 100644 --- a/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj +++ b/enterprise/backend/test/metabase_enterprise/sso/integrations/saml_test.clj @@ -5,12 +5,11 @@ [clojure.test :refer :all] [metabase-enterprise.sso.integrations.saml :as saml.mt] [metabase-enterprise.sso.integrations.sso-settings :as sso-settings] + [metabase.api.ldap] [metabase.http-client :as client] [metabase.models.permissions-group :refer [PermissionsGroup]] - [metabase.models.permissions-group-membership - :refer [PermissionsGroupMembership]] + [metabase.models.permissions-group-membership :refer [PermissionsGroupMembership]] [metabase.models.user :refer [User]] - [metabase.plugins.classloader :as classloader] [metabase.public-settings :as public-settings] [metabase.public-settings.premium-features :as premium-features] [metabase.server.middleware.session :as mw.session] @@ -30,10 +29,18 @@ (set! *warn-on-reflection* true) -(use-fixtures :once (fixtures/initialize :test-users)) +(comment metabase.api.ldap/keep-me) ; for [[metabase.api.ldap/ldap-enabled]] -(defn- disable-other-sso-types [thunk] - (classloader/require 'metabase.api.ldap) +(use-fixtures :once (fixtures/initialize :test-users :web-server)) + +(defn- disable-api-url-prefix + [thunk] + (binding [client/*url-prefix* ""] + (thunk))) + +(use-fixtures :each disable-api-url-prefix) + +(defn- do-with-other-sso-types-disabled! [thunk] (let [current-features (premium-features/*token-features*)] ;; The :sso-jwt token is needed to set the jwt-enabled setting (mt/test-helpers-set-global-values! @@ -43,21 +50,15 @@ (mt/with-premium-features current-features (thunk))))))) -(use-fixtures :each disable-other-sso-types) - -(defn- disable-api-url-prefix - [thunk] - (binding [client/*url-prefix* ""] - (thunk))) - -(use-fixtures :once disable-api-url-prefix) +(defmacro ^:private with-other-sso-types-disabled! [& body] + `(do-with-other-sso-types-disabled! (fn [] ~@body))) (def ^:private default-idp-uri "http://test.idp.metabase.com") (def ^:private default-redirect-uri "http://localhost:3000/test") (def ^:private default-idp-uri-with-param (str default-idp-uri "?someparam=true")) (def ^:private default-idp-cert (slurp "test_resources/sso/auth0-public-idp.cert")) -(defn call-with-default-saml-config [f] +(defn call-with-default-saml-config! [f] (let [current-features (premium-features/*token-features*)] (mt/with-premium-features #{:sso-saml} (mt/with-temporary-setting-values [saml-enabled true @@ -69,28 +70,29 @@ (mt/with-premium-features current-features (f)))))) -(defmacro with-default-saml-config [& body] - `(call-with-default-saml-config +(defmacro with-default-saml-config! [& body] + `(call-with-default-saml-config! (fn [] ~@body))) (defn call-with-login-attributes-cleared! "If login_attributes remain after these tests run, depending on the order that the tests run, lots of tests will fail as the login_attributes data from this tests is unexpected in those other tests" - [f] + [thunk] (try - (f) + (thunk) (finally - (u/ignore-exceptions (do (t2/update! User {} {:login_attributes nil}) - (t2/update! User {:email "rasta@metabase.com"} {:first_name "Rasta" :last_name "Toucan" :sso_source nil})))))) + (u/ignore-exceptions + (t2/update! User {} {:login_attributes nil}) + (t2/update! User {:email "rasta@metabase.com"} {:first_name "Rasta" :last_name "Toucan" :sso_source nil}))))) -(defmacro with-saml-default-setup [& body] +(defmacro with-saml-default-setup! [& body] ;; most saml tests make actual http calls, so ensuring any nested with-temp doesn't create transaction `(mt/test-helpers-set-global-values! (mt/with-additional-premium-features #{:sso-saml} (call-with-login-attributes-cleared! (fn [] - (call-with-default-saml-config + (call-with-default-saml-config! (fn [] ~@body))))))) @@ -99,16 +101,15 @@ [resp] (string? (get-in resp [:cookies mw.session/metabase-session-cookie :value]))) -(defn- do-with-some-validators-disabled +(defn- do-with-some-validators-disabled! "The sample responses all have `InResponseTo=\"_1\"` and invalid assertion signatures (they were edited by hand) so manually add `_1` to the state manager and turn off the <Assertion> signature validator so we can actually run tests." - {:style/indent [:defn 2]} - ([f] - (do-with-some-validators-disabled nil #{:signature :not-on-or-after :recipient :issuer} - f)) + ([thunk] + (do-with-some-validators-disabled! nil #{:signature :not-on-or-after :recipient :issuer} + thunk)) - ([disabled-response-validators disabled-assertion-validators f] + ([disabled-response-validators disabled-assertion-validators thunk] (let [orig saml/validate remove-validators (fn [options] (-> options @@ -120,43 +121,45 @@ ([response idp-cert sp-private-key options] (let [options (merge saml/default-validation-options options)] (orig response idp-cert sp-private-key (remove-validators options)))))] - (f))))) + (thunk))))) -(deftest validate-certificate-test +(deftest ^:parallel validate-certificate-test (testing "make sure our test certificate is actually valid" (is (some? (#'sso-settings/validate-saml-idp-cert default-idp-cert))))) (deftest require-valid-premium-features-token-test (testing "SSO requests fail if they don't have a valid premium-features token" - (mt/with-premium-features #{} - (with-default-saml-config - (is (= "SSO has not been enabled and/or configured" - (client/client :get 400 "/auth/sso"))))))) + (with-other-sso-types-disabled! + (mt/with-premium-features #{} + (with-default-saml-config! + (is (= "SSO has not been enabled and/or configured" + (client/client :get 400 "/auth/sso")))))))) (deftest require-saml-enabled-test - (mt/with-premium-features #{:sso-saml} - (testing "SSO requests fail if SAML hasn't been configured or enabled" - (mt/with-temporary-setting-values [saml-enabled false - saml-identity-provider-uri nil - saml-identity-provider-certificate nil] - (is (some? (client/client :get 400 "/auth/sso"))))) - - (testing "SSO requests fail if SAML has been configured but not enabled" - (mt/with-temporary-setting-values [saml-enabled false - saml-identity-provider-uri default-idp-uri - saml-identity-provider-certificate default-idp-cert] - (is (some? (client/client :get 400 "/auth/sso"))))) - - (testing "SSO requests fail if SAML is enabled but hasn't been configured" - (mt/with-temporary-setting-values [saml-enabled true - saml-identity-provider-uri nil] - (is (some? (client/client :get 400 "/auth/sso"))))) - - (testing "The IDP provider certificate must also be included for SSO to be configured" - (mt/with-temporary-setting-values [saml-enabled true - saml-identity-provider-uri default-idp-uri - saml-identity-provider-certificate nil] - (is (some? (client/client :get 400 "/auth/sso"))))))) + (with-other-sso-types-disabled! + (mt/with-premium-features #{:sso-saml} + (testing "SSO requests fail if SAML hasn't been configured or enabled" + (mt/with-temporary-setting-values [saml-enabled false + saml-identity-provider-uri nil + saml-identity-provider-certificate nil] + (is (some? (client/client :get 400 "/auth/sso"))))) + + (testing "SSO requests fail if SAML has been configured but not enabled" + (mt/with-temporary-setting-values [saml-enabled false + saml-identity-provider-uri default-idp-uri + saml-identity-provider-certificate default-idp-cert] + (is (some? (client/client :get 400 "/auth/sso"))))) + + (testing "SSO requests fail if SAML is enabled but hasn't been configured" + (mt/with-temporary-setting-values [saml-enabled true + saml-identity-provider-uri nil] + (is (some? (client/client :get 400 "/auth/sso"))))) + + (testing "The IDP provider certificate must also be included for SSO to be configured" + (mt/with-temporary-setting-values [saml-enabled true + saml-identity-provider-uri default-idp-uri + saml-identity-provider-certificate nil] + (is (some? (client/client :get 400 "/auth/sso")))))))) ;; TODO - maybe this belongs in a util namespace? (defn- uri->params-map @@ -168,81 +171,85 @@ (for [^BasicNameValuePair pair (-> (URL. uri-str) .getQuery (URLEncodedUtils/parse StandardCharsets/UTF_8))] [(keyword (.getName pair)) (.getValue pair)]))) -(deftest uri->params-map-test +(deftest ^:parallel uri->params-map-test (is (= {:a "b", :c "d"} (uri->params-map "http://localhost?a=b&c=d")))) (deftest request-xml-test (testing "Make sure the requests we generate look correct" - (with-saml-default-setup - (mt/with-temporary-setting-values [site-url "http://localhost:3000"] - (let [orig saml/request] - (with-redefs [saml/request (fn [m] - (testing "Request ID should be of the format id-<uuid>" - (is (re= (re-pattern (str "^id-" u/uuid-regex "$")) - (:request-id m)))) - (mt/with-clock #t "2020-09-30T17:53:32Z" - (orig (assoc m :request-id "id-419507d5-1d2a-43c4-bcde-3e5b9746bb47"))))] - (let [request (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect default-redirect-uri) - location (get-in request [:headers "Location"]) - base-64 (-> location uri->params-map :SAMLRequest) - xml (-> base-64 - codec/url-decode - encode-decode/base64->inflate->str - (str/replace #"\n+" "") - (str/replace #">\s+<" "><"))] - (is (= (str "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" - "<samlp:AuthnRequest" - " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"" - " AssertionConsumerServiceURL=\"http://localhost:3000/auth/sso\"" - " Destination=\"http://test.idp.metabase.com\"" - " ID=\"id-419507d5-1d2a-43c4-bcde-3e5b9746bb47\"" - " IssueInstant=\"2020-09-30T17:53:32Z\"" - " ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"" - " ProviderName=\"Metabase\"" - " Version=\"2.0\">" - "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">Metabase</saml:Issuer>" - "</samlp:AuthnRequest>") - xml))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (mt/with-temporary-setting-values [site-url "http://localhost:3000"] + (let [orig saml/request] + (with-redefs [saml/request (fn [m] + (testing "Request ID should be of the format id-<uuid>" + (is (re= (re-pattern (str "^id-" u/uuid-regex "$")) + (:request-id m)))) + (mt/with-clock #t "2020-09-30T17:53:32Z" + (orig (assoc m :request-id "id-419507d5-1d2a-43c4-bcde-3e5b9746bb47"))))] + (let [request (client/client-real-response :get 302 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :redirect default-redirect-uri) + location (get-in request [:headers "Location"]) + base-64 (-> location uri->params-map :SAMLRequest) + xml (-> base-64 + codec/url-decode + encode-decode/base64->inflate->str + (str/replace #"\n+" "") + (str/replace #">\s+<" "><"))] + (is (= (str "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<samlp:AuthnRequest" + " xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"" + " AssertionConsumerServiceURL=\"http://localhost:3000/auth/sso\"" + " Destination=\"http://test.idp.metabase.com\"" + " ID=\"id-419507d5-1d2a-43c4-bcde-3e5b9746bb47\"" + " IssueInstant=\"2020-09-30T17:53:32Z\"" + " ProtocolBinding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"" + " ProviderName=\"Metabase\"" + " Version=\"2.0\">" + "<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">Metabase</saml:Issuer>" + "</samlp:AuthnRequest>") + xml)))))))))) (deftest redirect-test (testing "With SAML configured, a GET request should result in a redirect to the IDP" - (with-saml-default-setup - (let [result (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect default-redirect-uri) - redirect-url (get-in result [:headers "Location"])] - (is (str/starts-with? redirect-url default-idp-uri)))))) - -(deftest redirect-append-paramters-test - (testing (str "When the identity provider already includes a query parameter, the SAML code should spot that and " - "append more parameters onto the query string (rather than always include a `?newparam=here`).") - (with-saml-default-setup - (mt/with-temporary-setting-values [saml-identity-provider-uri default-idp-uri-with-param] + (with-other-sso-types-disabled! + (with-saml-default-setup! (let [result (client/client-real-response :get 302 "/auth/sso" {:request-options {:redirect-strategy :none}} :redirect default-redirect-uri) redirect-url (get-in result [:headers "Location"])] - (is (= #{:someparam :SAMLRequest :RelayState} - (set (keys (uri->params-map redirect-url)))))))))) + (is (str/starts-with? redirect-url default-idp-uri))))))) + +(deftest redirect-append-paramters-test + (testing (str "When the identity provider already includes a query parameter, the SAML code should spot that and " + "append more parameters onto the query string (rather than always include a `?newparam=here`).") + (with-other-sso-types-disabled! + (with-saml-default-setup! + (mt/with-temporary-setting-values [saml-identity-provider-uri default-idp-uri-with-param] + (let [result (client/client-real-response :get 302 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :redirect default-redirect-uri) + redirect-url (get-in result [:headers "Location"])] + (is (= #{:someparam :SAMLRequest :RelayState} + (set (keys (uri->params-map redirect-url))))))))))) ;; The RelayState is data we include in the redirect request to the IDP. The IDP will include the RelayState in it's ;; response via the POST. This allows the FE to track what the original route the user was trying to access was and ;; redirect the user back to that original URL after successful authentication (deftest relay-state-test - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (let [result (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect default-redirect-uri) - redirect-url (get-in result [:headers "Location"])] - (testing (format "result = %s" (pr-str result)) - (is (string? redirect-url)) - (is (= default-redirect-uri - (saml/base64->str (:RelayState (uri->params-map redirect-url))))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (let [result (client/client-real-response :get 302 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :redirect default-redirect-uri) + redirect-url (get-in result [:headers "Location"])] + (testing (format "result = %s" (pr-str result)) + (is (string? redirect-url)) + (is (= default-redirect-uri + (saml/base64->str (:RelayState (uri->params-map redirect-url)))))))))))) (defn- saml-response-from-file [filename] (u/encode-base64 (slurp filename))) @@ -289,120 +296,108 @@ (deftest validate-request-id-test (testing "Sample response should fail because _1 isn't a request ID that we issued." - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (testing (str "After a successful login with the identity provider, the SAML provider will POST to the " - "`/auth/sso` route.") - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= default-redirect-uri - (get-in response [:headers "Location"]))) - (is (= (some-saml-attributes "rasta") - (saml-login-attributes "rasta@metabase.com")))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (testing (str "After a successful login with the identity provider, the SAML provider will POST to the " + "`/auth/sso` route.") + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= default-redirect-uri + (get-in response [:headers "Location"]))) + (is (= (some-saml-attributes "rasta") + (saml-login-attributes "rasta@metabase.com"))))))))))) (deftest validate-signatures-test ;; they were edited by hand I think, so the signatures are now incorrect (?) (testing "The sample responses should normally fail because the <Assertion> signatures don't match" - (with-saml-default-setup - (do-with-some-validators-disabled nil #{:not-on-or-after :recipient :issuer} - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) - default-redirect-uri) - response (client/client-real-response :post 401 "/auth/sso" req-options)] - (testing (format "response =\n%s" (u/pprint-to-str response)) - (is (not (successful-login? response)))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + nil #{:not-on-or-after :recipient :issuer} + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) + default-redirect-uri) + response (client/client-real-response :post 401 "/auth/sso" req-options)] + (testing (format "response =\n%s" (u/pprint-to-str response)) + (is (not (successful-login? response))))))))))) (deftest validate-not-on-or-after-test - (with-saml-default-setup - (testing "The sample responses should normally fail because the <Assertion> NotOnOrAfter has passed" - (do-with-some-validators-disabled nil #{:signature :recipient} - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (not (successful-login? (client/client-real-response :post 401 "/auth/sso" req-options)))))))) - (testing "If we time-travel then the sample responses *should* work" - (let [orig saml/validate] - (with-redefs [saml/validate (fn [& args] - (mt/with-clock #t "2018-07-01T00:00:00.000Z" - (apply orig args)))] - (do-with-some-validators-disabled nil #{:signature :recipient :issuer} - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (testing "The sample responses should normally fail because the <Assertion> NotOnOrAfter has passed" + (do-with-some-validators-disabled! + nil #{:signature :recipient} + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (not (successful-login? (client/client-real-response :post 401 "/auth/sso" req-options))))))))))) + +(deftest validate-not-on-or-after-test-2 + (with-other-sso-types-disabled! + (with-saml-default-setup! + (testing "If we time-travel then the sample responses *should* work" + (let [orig saml/validate] + (with-redefs [saml/validate (fn [& args] + (mt/with-clock #t "2018-07-01T00:00:00.000Z" + (apply orig args)))] + (do-with-some-validators-disabled! + nil #{:signature :recipient :issuer} + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options)))))))))))) (deftest validate-recipient-test - (with-saml-default-setup - (testing (str "The sample responses all have <Recipient> of localhost:3000. " - "If (site-url) is set to something different, this should fail.") - (do-with-some-validators-disabled nil #{:signature :not-on-or-after :issuer} - (fn [] - (testing "with incorrect acs-url" - (mt/with-temporary-setting-values [site-url "http://localhost:9876"] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (not (successful-login? (client/client-real-response :post 401 "/auth/sso" req-options))))))) - (testing "with correct acs-url" - (mt/with-temporary-setting-values [site-url "http://localhost:3000"] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (testing (str "The sample responses all have <Recipient> of localhost:3000. " + "If (site-url) is set to something different, this should fail.") + (do-with-some-validators-disabled! + nil #{:signature :not-on-or-after :issuer} + (fn [] + (testing "with incorrect acs-url" + (mt/with-temporary-setting-values [site-url "http://localhost:9876"] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (not (successful-login? (client/client-real-response :post 401 "/auth/sso" req-options))))))) + (testing "with correct acs-url" + (mt/with-temporary-setting-values [site-url "http://localhost:3000"] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options)))))))))))) (deftest validate-issuer-test - (with-saml-default-setup - (testing "If the `saml-identity-provider-issuer` Setting is set, we should validate <Issuer> in Responses" - (do-with-some-validators-disabled nil #{:signature :not-on-or-after :recipient} - (letfn [(login [expected-status-code] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri))] - (client/client-real-response :post expected-status-code "/auth/sso" req-options)))] - (fn [] - (testing "<Issuer> matches saml-identity-provider-issuer" - (mt/with-temporary-setting-values [saml-identity-provider-issuer "urn:saml-metabase-test.auth0.com"] - (is (successful-login? (login 302))))) - (testing "<Issuer> does not match saml-identity-provider-issuer" - (mt/with-temporary-setting-values [saml-identity-provider-issuer "WRONG"] - (is (not (successful-login? (login 401)))))) - (testing "saml-identity-provider-issuer is not set: shouldn't do any validation" - (mt/with-temporary-setting-values [saml-identity-provider-issuer nil] - (is (successful-login? (login 302))))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (testing "If the `saml-identity-provider-issuer` Setting is set, we should validate <Issuer> in Responses" + (do-with-some-validators-disabled! + nil #{:signature :not-on-or-after :recipient} + (letfn [(login [expected-status-code] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri))] + (client/client-real-response :post expected-status-code "/auth/sso" req-options)))] + (fn [] + (testing "<Issuer> matches saml-identity-provider-issuer" + (mt/with-temporary-setting-values [saml-identity-provider-issuer "urn:saml-metabase-test.auth0.com"] + (is (successful-login? (login 302))))) + (testing "<Issuer> does not match saml-identity-provider-issuer" + (mt/with-temporary-setting-values [saml-identity-provider-issuer "WRONG"] + (is (not (successful-login? (login 401)))))) + (testing "saml-identity-provider-issuer is not set: shouldn't do any validation" + (mt/with-temporary-setting-values [saml-identity-provider-issuer nil] + (is (successful-login? (login 302)))))))))))) ;; Part of accepting the POST is validating the response and the relay state so we can redirect the user to their ;; original destination (deftest login-test (testing "After a successful login with the identity provider, the SAML provider will POST to the `/auth/sso` route." - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= default-redirect-uri - (get-in response [:headers "Location"]))) - (is (= (some-saml-attributes "rasta") - (saml-login-attributes "rasta@metabase.com")))))))) - (testing "Still works with whitespace in the SAML post response (#23451)" - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (let [req-options (saml-post-request-options (whitespace-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= default-redirect-uri - (get-in response [:headers "Location"]))) - (is (= (some-saml-attributes "rasta") - (saml-login-attributes "rasta@metabase.com"))))))))) - -(deftest jwt-saml-both-enabled-saml-success-test - (mt/with-additional-premium-features #{:sso-jwt} - (testing "with SAML and JWT configured, a GET request without JWT params successfully logins with SAML." - (with-saml-default-setup - (do-with-some-validators-disabled + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! (fn [] (let [req-options (saml-post-request-options (saml-test-response) (saml/str->base64 default-redirect-uri)) @@ -413,118 +408,154 @@ (is (= (some-saml-attributes "rasta") (saml-login-attributes "rasta@metabase.com")))))))))) +(deftest login-test-2 + (testing "Still works with whitespace in the SAML post response (#23451)" + (with-saml-default-setup! + (with-other-sso-types-disabled! + (do-with-some-validators-disabled! + (fn [] + (let [req-options (saml-post-request-options (whitespace-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= default-redirect-uri + (get-in response [:headers "Location"]))) + (is (= (some-saml-attributes "rasta") + (saml-login-attributes "rasta@metabase.com")))))))))) + +(deftest jwt-saml-both-enabled-saml-success-test + (with-other-sso-types-disabled! + (mt/with-additional-premium-features #{:sso-jwt} + (testing "with SAML and JWT configured, a GET request without JWT params successfully logins with SAML." + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= default-redirect-uri + (get-in response [:headers "Location"]))) + (is (= (some-saml-attributes "rasta") + (saml-login-attributes "rasta@metabase.com"))))))))))) + (deftest login-invalid-relay-state-test (testing (str "if the RelayState is not set or is invalid, you are redirected back to the home page rather than " "failing the entire login") - (doseq [relay-state ["something-random_#!@__^^" - "" - " " - "/" - "https://badsite.com" - "//badsite.com" - "https:///badsite.com"]] - (testing (format "\nRelayState = %s" (pr-str relay-state)) - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) relay-state) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= (public-settings/site-url) - (get-in response [:headers "Location"]))) - (is (= (some-saml-attributes "rasta") - (saml-login-attributes "rasta@metabase.com")))))))))) - + (with-other-sso-types-disabled! + (doseq [relay-state ["something-random_#!@__^^" + "" + " " + "/" + "https://badsite.com" + "//badsite.com" + "https:///badsite.com"]] + (testing (format "\nRelayState = %s" (pr-str relay-state)) + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) relay-state) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= (public-settings/site-url) + (get-in response [:headers "Location"]))) + (is (= (some-saml-attributes "rasta") + (saml-login-attributes "rasta@metabase.com")))))))))))) + +(deftest login-invalid-relay-state-test-2 (testing "if the RelayState leads us to the wrong host, avoid the open redirect (boat#160)" - (doseq [redirect-url ["https://badsite.com" - "//badsite.com" - "https:///badsite.com"]] - (with-saml-default-setup - (mt/with-temporary-setting-values [site-url "http://localhost:3000"] - (do-with-some-validators-disabled - (fn [] - (let [get-response (client/client :get 400 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect redirect-url)] - (testing (format "\n%s should not redirect" redirect-url) - (is (= "Invalid redirect URL" (:message get-response)))))))))))) + (with-other-sso-types-disabled! + (doseq [redirect-url ["https://badsite.com" + "//badsite.com" + "https:///badsite.com"]] + (with-saml-default-setup! + (mt/with-temporary-setting-values [site-url "http://localhost:3000"] + (do-with-some-validators-disabled! + (fn [] + (let [get-response (client/client :get 400 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :redirect redirect-url)] + (testing (format "\n%s should not redirect" redirect-url) + (is (= "Invalid redirect URL" (:message get-response))))))))))))) (deftest login-create-account-test (testing "A new account will be created for a SAML user we haven't seen before" - (mt/with-premium-features #{:audit-app} - (do-with-some-validators-disabled - (fn [] - (with-saml-default-setup - (try - (is (not (t2/exists? User :%lower.email "newuser@metabase.com"))) - (let [req-options (saml-post-request-options (new-user-saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options)))) - (let [new-user (t2/select-one User :email "newuser@metabase.com")] - (is (= {:email "newuser@metabase.com" - :first_name "New" - :is_qbnewb true - :is_superuser false - :id true - :last_name "User" - :date_joined true - :common_name "New User"} - (-> (mt/boolean-ids-and-timestamps new-user) - (dissoc :last_login)))) - (testing "User Invite Event is logged." - (is (= {:details {:email "newuser@metabase.com" - :first_name "New" - :last_name "User" - :user_group_memberships [{:id 1}] - :sso_source "saml"} - :model "User" - :model_id (:id new-user) - :topic :user-invited - :user_id nil} - (mt/latest-audit-log-entry :user-invited (:id new-user)))))) - (testing "attributes" - (is (= (some-saml-attributes "newuser") - (saml-login-attributes "newuser@metabase.com")))) - (finally - (t2/delete! User :%lower.email "newuser@metabase.com"))))))))) + (with-other-sso-types-disabled! + (mt/with-premium-features #{:audit-app} + (do-with-some-validators-disabled! + (fn [] + (with-saml-default-setup! + (try + (is (not (t2/exists? User :%lower.email "newuser@metabase.com"))) + (let [req-options (saml-post-request-options (new-user-saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options)))) + (let [new-user (t2/select-one User :email "newuser@metabase.com")] + (is (= {:email "newuser@metabase.com" + :first_name "New" + :is_qbnewb true + :is_superuser false + :id true + :last_name "User" + :date_joined true + :common_name "New User"} + (-> (mt/boolean-ids-and-timestamps new-user) + (dissoc :last_login)))) + (testing "User Invite Event is logged." + (is (= {:details {:email "newuser@metabase.com" + :first_name "New" + :last_name "User" + :user_group_memberships [{:id 1}] + :sso_source "saml"} + :model "User" + :model_id (:id new-user) + :topic :user-invited + :user_id nil} + (mt/latest-audit-log-entry :user-invited (:id new-user)))))) + (testing "attributes" + (is (= (some-saml-attributes "newuser") + (saml-login-attributes "newuser@metabase.com")))) + (finally + (t2/delete! User :%lower.email "newuser@metabase.com")))))))))) (deftest login-update-account-test (testing "A new 'Unknown' name account will be created for a SAML user with no configured first or last name" - (do-with-some-validators-disabled - (fn [] - (with-saml-default-setup - (try - (is (not (t2/exists? User :%lower.email "newuser@metabase.com"))) + (with-other-sso-types-disabled! + (do-with-some-validators-disabled! + (fn [] + (with-saml-default-setup! + (try + (is (not (t2/exists? User :%lower.email "newuser@metabase.com"))) ;; login with a user with no givenname or surname attributes - (let [req-options (saml-post-request-options (new-user-no-names-saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))) - (is (= [{:email "newuser@metabase.com" - :first_name nil - :is_qbnewb true - :is_superuser false - :id true - :last_name nil - :date_joined true - :common_name "newuser@metabase.com"}] - (->> (mt/boolean-ids-and-timestamps (t2/select User :email "newuser@metabase.com")) - (map #(dissoc % :last_login)))))) + (let [req-options (saml-post-request-options (new-user-no-names-saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))) + (is (= [{:email "newuser@metabase.com" + :first_name nil + :is_qbnewb true + :is_superuser false + :id true + :last_name nil + :date_joined true + :common_name "newuser@metabase.com"}] + (->> (mt/boolean-ids-and-timestamps (t2/select User :email "newuser@metabase.com")) + (map #(dissoc % :last_login)))))) ;; login with the same user, but now givenname and surname attributes exist - (let [req-options (saml-post-request-options (new-user-saml-test-response) - (saml/str->base64 default-redirect-uri))] - (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))) - (is (= [{:email "newuser@metabase.com" - :first_name "New" - :is_qbnewb true - :is_superuser false - :id true - :last_name "User" - :date_joined true - :common_name "New User"}] - (->> (mt/boolean-ids-and-timestamps (t2/select User :email "newuser@metabase.com")) - (map #(dissoc % :last_login)))))) - (finally - (t2/delete! User :%lower.email "newuser@metabase.com")))))))) + (let [req-options (saml-post-request-options (new-user-saml-test-response) + (saml/str->base64 default-redirect-uri))] + (is (successful-login? (client/client-real-response :post 302 "/auth/sso" req-options))) + (is (= [{:email "newuser@metabase.com" + :first_name "New" + :is_qbnewb true + :is_superuser false + :id true + :last_name "User" + :date_joined true + :common_name "New User"}] + (->> (mt/boolean-ids-and-timestamps (t2/select User :email "newuser@metabase.com")) + (map #(dissoc % :last_login)))))) + (finally + (t2/delete! User :%lower.email "newuser@metabase.com"))))))))) (defn- group-memberships [user-or-id] (when-let [group-ids (seq (t2/select-fn-set :group_id PermissionsGroupMembership :user_id (u/the-id user-or-id)))] @@ -532,188 +563,200 @@ (deftest login-should-sync-single-group-membership (testing "saml group sync works when there's just a single group, which gets interpreted as a string" - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (t2.with-temp/with-temp [PermissionsGroup group-1 {:name (str ::group-1)}] - (mt/with-temporary-setting-values [saml-group-sync true - saml-group-mappings {"group_1" [(u/the-id group-1)]} - saml-attribute-group "GroupMembership"] - (try - ;; user doesn't exist until SAML request - (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com"))) - (let [req-options (saml-post-request-options (new-user-with-single-group-saml-test-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= #{"All Users" - ":metabase-enterprise.sso.integrations.saml-test/group-1"} - (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) - (finally - (t2/delete! User :%lower.email "newuser@metabase.com")))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (t2.with-temp/with-temp [PermissionsGroup group-1 {:name (str ::group-1)}] + (mt/with-temporary-setting-values [saml-group-sync true + saml-group-mappings {"group_1" [(u/the-id group-1)]} + saml-attribute-group "GroupMembership"] + (try + ;; user doesn't exist until SAML request + (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com"))) + (let [req-options (saml-post-request-options (new-user-with-single-group-saml-test-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= #{"All Users" + ":metabase-enterprise.sso.integrations.saml-test/group-1"} + (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) + (finally + (t2/delete! User :%lower.email "newuser@metabase.com"))))))))))) (deftest login-should-sync-multiple-group-membership (testing "saml group sync works when there are multiple groups, which gets interpreted as a list of strings" (testing "when only one Attribute node exists" - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (mt/with-temp [PermissionsGroup group-1 {:name (str ::group-1)} - PermissionsGroup group-2 {:name (str ::group-2)}] - (mt/with-temporary-setting-values [saml-group-sync true - saml-group-mappings {"group_1" [(u/the-id group-1)] - "group_2" [(u/the-id group-2)]} - saml-attribute-group "GroupMembership"] - (try - (testing "user doesn't exist until SAML request" - (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com")))) - (let [req-options (saml-post-request-options (new-user-with-groups-saml-test-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= #{"All Users" - ":metabase-enterprise.sso.integrations.saml-test/group-1" - ":metabase-enterprise.sso.integrations.saml-test/group-2"} - (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) - (finally - (t2/delete! User :%lower.email "newuser@metabase.com"))))))))) - (testing "when several Attribute nodes exist (issue #20744)" - (with-saml-default-setup - (do-with-some-validators-disabled - (fn [] - (mt/with-temp [PermissionsGroup group-1 {:name (str ::group-1)} - PermissionsGroup group-2 {:name (str ::group-2)}] - (mt/with-temporary-setting-values [saml-group-sync true - saml-group-mappings {"group_1" [(u/the-id group-1)] - "group_2" [(u/the-id group-2)]} - saml-attribute-group "GroupMembership"] - (try - (testing "user doesn't exist until SAML request" - (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com")))) - (let [req-options (saml-post-request-options (new-user-with-groups-in-separate-attribute-nodes-saml-test-response) - (saml/str->base64 default-redirect-uri)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= #{"All Users" - ":metabase-enterprise.sso.integrations.saml-test/group-1" - ":metabase-enterprise.sso.integrations.saml-test/group-2"} - (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) - (finally - (t2/delete! User :%lower.email "newuser@metabase.com"))))))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (mt/with-temp [PermissionsGroup group-1 {:name (str ::group-1)} + PermissionsGroup group-2 {:name (str ::group-2)}] + (mt/with-temporary-setting-values [saml-group-sync true + saml-group-mappings {"group_1" [(u/the-id group-1)] + "group_2" [(u/the-id group-2)]} + saml-attribute-group "GroupMembership"] + (try + (testing "user doesn't exist until SAML request" + (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com")))) + (let [req-options (saml-post-request-options (new-user-with-groups-saml-test-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= #{"All Users" + ":metabase-enterprise.sso.integrations.saml-test/group-1" + ":metabase-enterprise.sso.integrations.saml-test/group-2"} + (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) + (finally + (t2/delete! User :%lower.email "newuser@metabase.com")))))))))))) + +(deftest login-should-sync-multiple-group-membership-2 + (testing "saml group sync works when there are multiple groups, which gets interpreted as a list of strings" + (testing "when several Attribute nodes exist (#20744)" + (with-other-sso-types-disabled! + (with-saml-default-setup! + (do-with-some-validators-disabled! + (fn [] + (mt/with-temp [PermissionsGroup group-1 {:name (str ::group-1)} + PermissionsGroup group-2 {:name (str ::group-2)}] + (mt/with-temporary-setting-values [saml-group-sync true + saml-group-mappings {"group_1" [(u/the-id group-1)] + "group_2" [(u/the-id group-2)]} + saml-attribute-group "GroupMembership"] + (try + (testing "user doesn't exist until SAML request" + (is (not (t2/select-one-pk User :%lower.email "newuser@metabase.com")))) + (let [req-options (saml-post-request-options (new-user-with-groups-in-separate-attribute-nodes-saml-test-response) + (saml/str->base64 default-redirect-uri)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= #{"All Users" + ":metabase-enterprise.sso.integrations.saml-test/group-1" + ":metabase-enterprise.sso.integrations.saml-test/group-2"} + (group-memberships (t2/select-one-pk User :email "newuser@metabase.com"))))) + (finally + (t2/delete! User :%lower.email "newuser@metabase.com")))))))))))) (deftest relay-state-e2e-test (testing "Redirect URL (RelayState) should work correctly end-to-end (#13666)" - (with-saml-default-setup + (with-other-sso-types-disabled! + (with-saml-default-setup! ;; The test HTTP client will automatically URL encode these for us. - (doseq [redirect-url ["http://localhost:3001/collection/root" - default-redirect-uri - "http://localhost:3001/"]] - (testing (format "\nredirect URL = %s" redirect-url) - (let [result (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect redirect-url) - location (get-in result [:headers "Location"]) - _ (is (string? location)) - params-map (uri->params-map location)] - (testing (format "\nresult =\n%s" (u/pprint-to-str params-map)) - (testing "\nRelay state URL should be base-64 encoded" - (is (= (saml/str->base64 redirect-url) - (:RelayState params-map)))) - (testing "\nPOST request should redirect to the original redirect URL" - (do-with-some-validators-disabled - (fn [] - (let [req-options (saml-post-request-options (saml-test-response) - (:RelayState params-map)) - response (client/client-real-response :post 302 "/auth/sso" req-options)] - (is (successful-login? response)) - (is (= redirect-url - (get-in response [:headers "Location"])))))))))))))) - -(deftest sso-subpath-e2e-test - (testing "Redirect URL should correcly append the site-url when the redirect is a relative path (#28650)" - (with-saml-default-setup - (doseq [redirect-url ["/collection/root" - "/test" - "/"]] - (testing (format "\nredirect URL = %s" redirect-url) - (mt/with-temporary-setting-values [site-url "http://localhost:3001/path"] + (doseq [redirect-url ["http://localhost:3001/collection/root" + default-redirect-uri + "http://localhost:3001/"]] + (testing (format "\nredirect URL = %s" redirect-url) (let [result (client/client-real-response :get 302 "/auth/sso" - {:request-options {:redirect-strategy :none}} - :redirect redirect-url) + {:request-options {:redirect-strategy :none}} + :redirect redirect-url) location (get-in result [:headers "Location"]) _ (is (string? location)) params-map (uri->params-map location)] (testing (format "\nresult =\n%s" (u/pprint-to-str params-map)) - (testing "\nPOST request should redirect to the original redirect URL with the correct site-url path" - (do-with-some-validators-disabled + (testing "\nRelay state URL should be base-64 encoded" + (is (= (saml/str->base64 redirect-url) + (:RelayState params-map)))) + (testing "\nPOST request should redirect to the original redirect URL" + (do-with-some-validators-disabled! (fn [] (let [req-options (saml-post-request-options (saml-test-response) (:RelayState params-map)) response (client/client-real-response :post 302 "/auth/sso" req-options)] (is (successful-login? response)) - (is (= (str "http://localhost:3001/path" redirect-url) + (is (= redirect-url (get-in response [:headers "Location"]))))))))))))))) +(deftest sso-subpath-e2e-test + (testing "Redirect URL should correcly append the site-url when the redirect is a relative path (#28650)" + (with-other-sso-types-disabled! + (with-saml-default-setup! + (doseq [redirect-url ["/collection/root" + "/test" + "/"]] + (testing (format "\nredirect URL = %s" redirect-url) + (mt/with-temporary-setting-values [site-url "http://localhost:3001/path"] + (let [result (client/client-real-response :get 302 "/auth/sso" + {:request-options {:redirect-strategy :none}} + :redirect redirect-url) + location (get-in result [:headers "Location"]) + _ (is (string? location)) + params-map (uri->params-map location)] + (testing (format "\nresult =\n%s" (u/pprint-to-str params-map)) + (testing "\nPOST request should redirect to the original redirect URL with the correct site-url path" + (do-with-some-validators-disabled! + (fn [] + (let [req-options (saml-post-request-options (saml-test-response) + (:RelayState params-map)) + response (client/client-real-response :post 302 "/auth/sso" req-options)] + (is (successful-login? response)) + (is (= (str "http://localhost:3001/path" redirect-url) + (get-in response [:headers "Location"])))))))))))))))) + (deftest create-new-saml-user-no-user-provisioning-test (testing "When user provisioning is disabled, throw an error if we attempt to create a new user." - (with-saml-default-setup - (with-redefs [sso-settings/saml-user-provisioning-enabled? (constantly false) - public-settings/site-name (constantly "test")] - (is - (thrown-with-msg? - clojure.lang.ExceptionInfo - #"Sorry, but you'll need a test account to view this page. Please contact your administrator." - (#'saml.mt/fetch-or-create-user! {:first-name "Test" - :last-name "user" - :email "test1234@metabsae.com"}))))))) + (with-other-sso-types-disabled! + (with-saml-default-setup! + (with-redefs [sso-settings/saml-user-provisioning-enabled? (constantly false) + public-settings/site-name (constantly "test")] + (is + (thrown-with-msg? + clojure.lang.ExceptionInfo + #"Sorry, but you'll need a test account to view this page. Please contact your administrator." + (#'saml.mt/fetch-or-create-user! {:first-name "Test" + :last-name "user" + :email "test1234@metabsae.com"})))))))) (deftest logout-should-delete-session-test-slo-enabled (testing "Successful SAML SLO logouts should delete the user's session, when saml-slo-enabled." - (mt/with-temporary-setting-values [saml-slo-enabled true] - (let [session-id (str (random-uuid))] - (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} - :model/Session _ {:user_id (:id user) :id session-id}] - (with-saml-default-setup - (is (t2/exists? :model/Session :id session-id)) - (let [req-options (-> (saml-post-request-options - (saml-slo-test-response) - (saml/str->base64 default-redirect-uri)) + (with-other-sso-types-disabled! + (mt/with-temporary-setting-values [saml-slo-enabled true] + (let [session-id (str (random-uuid))] + (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} + :model/Session _ {:user_id (:id user) :id session-id}] + (with-saml-default-setup! + (is (t2/exists? :model/Session :id session-id)) + (let [req-options (-> (saml-post-request-options + (saml-slo-test-response) + (saml/str->base64 default-redirect-uri)) ;; Client sends their session cookie during the SLO request redirect from the IDP. - (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id)) - response (client/client-real-response :post 302 "/auth/sso/handle_slo" req-options)] - (is (str/blank? (get-in response [:cookies mw.session/metabase-session-cookie :value])) - "After a successful log-out, you don't have a session") - (is (not (t2/exists? :model/Session :id session-id)) - "After a successful log-out, the session is deleted")))))))) + (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id)) + response (client/client-real-response :post 302 "/auth/sso/handle_slo" req-options)] + (is (str/blank? (get-in response [:cookies mw.session/metabase-session-cookie :value])) + "After a successful log-out, you don't have a session") + (is (not (t2/exists? :model/Session :id session-id)) + "After a successful log-out, the session is deleted"))))))))) (deftest logout-should-delete-session-test-slo-disabled (testing "Successful SAML SLO logouts should not delete the user's session, when not saml-slo-enabled." - (mt/with-temporary-setting-values [saml-slo-enabled false] - (let [session-id (str (random-uuid))] - (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} - :model/Session _ {:user_id (:id user) :id session-id}] - (with-saml-default-setup - (is (t2/exists? :model/Session :id session-id)) - (let [req-options (-> (saml-post-request-options - (saml-slo-test-response) - (saml/str->base64 default-redirect-uri)) + (with-other-sso-types-disabled! + (mt/with-temporary-setting-values [saml-slo-enabled false] + (let [session-id (str (random-uuid))] + (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} + :model/Session _ {:user_id (:id user) :id session-id}] + (with-saml-default-setup! + (is (t2/exists? :model/Session :id session-id)) + (let [req-options (-> (saml-post-request-options + (saml-slo-test-response) + (saml/str->base64 default-redirect-uri)) ;; Client sends their session cookie during the SLO request redirect from the IDP. - (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id)) - response (client/client-real-response :post 403 "/auth/sso/handle_slo" req-options)] - (is (str/blank? (get-in response [:cookies mw.session/metabase-session-cookie :value])) - "After a successful log-out, you don't have a session") - (is (t2/exists? :model/Session :id session-id) - "After a successful log-out, the session is deleted")))))))) + (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id)) + response (client/client-real-response :post 403 "/auth/sso/handle_slo" req-options)] + (is (str/blank? (get-in response [:cookies mw.session/metabase-session-cookie :value])) + "After a successful log-out, you don't have a session") + (is (t2/exists? :model/Session :id session-id) + "After a successful log-out, the session is deleted"))))))))) (deftest logout-should-delete-session-when-idp-slo-conf-missing-test (testing "Missing SAML SLO config logouts should still delete the user's session." - (let [session-id (str (random-uuid))] - (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} - :model/Session _ {:user_id (:id user) :id session-id}] - (is (t2/exists? :model/Session :id session-id)) - (let [req-options (-> (saml-post-request-options - (saml-test-response) - (saml/str->base64 default-redirect-uri)) - (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id))] - (client/client :post "/auth/sso/logout" req-options) - (is (not (t2/exists? :model/Session :id session-id)))))))) + (with-other-sso-types-disabled! + (let [session-id (str (random-uuid))] + (mt/with-temp [:model/User user {:email "saml_test@metabase.com" :sso_source "saml"} + :model/Session _ {:user_id (:id user) :id session-id}] + (is (t2/exists? :model/Session :id session-id)) + (let [req-options (-> (saml-post-request-options + (saml-test-response) + (saml/str->base64 default-redirect-uri)) + (assoc-in [:request-options :cookies mw.session/metabase-session-cookie :value] session-id))] + (client/client :post "/auth/sso/logout" req-options) + (is (not (t2/exists? :model/Session :id session-id))))))))) diff --git a/enterprise/backend/test/metabase_enterprise/stale/api_test.clj b/enterprise/backend/test/metabase_enterprise/stale/api_test.clj index 028ffc0b68ed9a7b32ed319886232c79372e26e6..2f14e499b8357dc6ad8cd328a3814e27f710e492 100644 --- a/enterprise/backend/test/metabase_enterprise/stale/api_test.clj +++ b/enterprise/backend/test/metabase_enterprise/stale/api_test.clj @@ -3,7 +3,7 @@ [clojure.test :refer [deftest testing is]] [metabase.analytics.snowplow-test :as snowplow-test] [metabase.models.collection :as collection] - [metabase.models.collection-test :refer [with-collection-hierarchy]] + [metabase.models.collection-test :refer [with-collection-hierarchy!]] [metabase.stale-test :as stale.test] [metabase.test :as mt] [metabase.util :as u])) @@ -21,7 +21,7 @@ (deftest can-fetch-stale-candidates (mt/with-premium-features #{:collection-cleanup} - (with-collection-hierarchy [{:keys [a b c d e]}] + (with-collection-hierarchy! [{:keys [a b c d e]}] (stale.test/with-stale-items [:model/Card card {:collection_id (:id a)} :model/Dashboard dashboard {:collection_id (:id a)}] (let [result (mt/user-http-request :crowberto :get 200 (str "collection/" (u/the-id a) "/stale"))] @@ -180,7 +180,7 @@ (deftest stale-items-limits-and-offsets-work-correctly (mt/with-premium-features #{:collection-cleanup} (testing "Limits and offsets work correctly" - (with-collection-hierarchy [{:keys [a]}] + (with-collection-hierarchy! [{:keys [a]}] (let [get-names-page (fn [limit offset] (->> (mt/user-http-request :crowberto :get 200 (str "collection/" (:id a) "/stale") :limit limit @@ -204,7 +204,7 @@ (deftest snowplow-events-are-emitted (mt/with-premium-features #{:collection-cleanup} - (with-collection-hierarchy [{:keys [a]}] + (with-collection-hierarchy! [{:keys [a]}] (snowplow-test/with-fake-snowplow-collector (mt/user-http-request :crowberto :get 200 (str "collection/" (u/the-id a) "/stale") :before_date "1988-01-21") diff --git a/enterprise/backend/test/metabase_enterprise/test.clj b/enterprise/backend/test/metabase_enterprise/test.clj index 92c277be72182add4f217d804d75923de723158b..053eaf8bced03abca2f43c8fc3122c507eb5b7b3 100644 --- a/enterprise/backend/test/metabase_enterprise/test.clj +++ b/enterprise/backend/test/metabase_enterprise/test.clj @@ -10,4 +10,4 @@ [sandbox.tu with-gtaps! with-gtaps-for-user! - with-user-attributes]) + with-user-attributes!]) diff --git a/modules/drivers/druid/test/metabase/driver/druid/query_processor_test.clj b/modules/drivers/druid/test/metabase/driver/druid/query_processor_test.clj index 596a9bf0b952cd585df9b05c6ac71c284c4273f3..6a0850bff3e710b00f1969175b19cf7845c7d1a8 100644 --- a/modules/drivers/druid/test/metabase/driver/druid/query_processor_test.clj +++ b/modules/drivers/druid/test/metabase/driver/druid/query_processor_test.clj @@ -89,14 +89,14 @@ [:= [:field 2 nil] "threecan"]])) ":or filters with no temporal filters should return nil")))) -(defn- do-query->native [query] +(defn- do-query->native! [query] (driver/with-driver :druid (tqpt/with-flattened-dbdef (with-redefs [druid.qp/random-query-id (constantly "<Query ID>")] (qp.compile/compile query))))) -(defmacro ^:private query->native [query] - `(do-query->native +(defmacro ^:private query->native! [query] + `(do-query->native! (mt/mbql-query ~'checkins ~query))) @@ -124,7 +124,7 @@ :aggregator {:type :count, :name "__count_0"}}]} :query-type ::druid.qp/topN :mbql? true} - (query->native + (query->native! #_:clj-kondo/ignore {:aggregation [[:* [:count $id] 10]] :breakout [$venue_price]})))))) @@ -148,7 +148,7 @@ :round true}]} :query-type ::druid.qp/topN :mbql? true} - (query->native + (query->native! #_:clj-kondo/ignore {:aggregation [[:aggregation-options [:distinct $checkins.venue_name] {:name "__count_0"}]] :breakout [$venue_category_name] @@ -175,7 +175,7 @@ {:dimension "user_name", :direction :ascending}]}} :query-type ::druid.qp/groupBy :mbql? true} - (query->native + (query->native! #_:clj-kondo/ignore {:aggregation [[:aggregation-options [:distinct $checkins.venue_name] {:name "__count_0"}]] :breakout [$venue_category_name $user_name] @@ -203,7 +203,7 @@ :limit 5}} :query-type ::druid.qp/groupBy :mbql? true} - (query->native + (query->native! {:aggregation [[:aggregation-options [:distinct $checkins.venue_name] {:name "__count_0"}]] :breakout [$venue_category_name $user_name] :order-by [[:desc [:aggregation 0]] [:asc $checkins.venue_category_name]] @@ -232,7 +232,7 @@ {:type :finalizingFieldAccess, :fieldName "__distinct_0"}]}]} :query-type ::druid.qp/total :mbql? true} - (query->native + (query->native! {:aggregation [[:+ 1 [:aggregation-options [:distinct $checkins.venue_name] {:name "__distinct_0"}]]]}))))))) (defn- table-rows-sample [] diff --git a/modules/drivers/redshift/test/metabase/driver/redshift_test.clj b/modules/drivers/redshift/test/metabase/driver/redshift_test.clj index 9db1a8422bbd0f49f52b397de3e663b70e28dc1f..59a8512a9b09eaae7cc6f782e6532f2e5ccf90ef 100644 --- a/modules/drivers/redshift/test/metabase/driver/redshift_test.clj +++ b/modules/drivers/redshift/test/metabase/driver/redshift_test.clj @@ -53,7 +53,7 @@ (#'sql.qp/add-default-select :redshift) (sql.qp/format-honeysql :redshift))))) -(defn- query->native [query] +(defn- query->native! [query] (let [native-query (atom nil) check-sql-fn (fn [_ _ sql & _] (reset! native-query sql) @@ -94,7 +94,7 @@ (str/replace #"\Q{{schema}}\E" (redshift.test/unique-session-schema))))] (is (= expected (sql->lines - (query->native + (query->native! (assoc (mt/mbql-query users {:limit 2000}) :parameters [{:type "id" @@ -317,7 +317,7 @@ (mt/test-driver :redshift (testing "metabase_cache tables should be excluded from the describe-database results" (mt/dataset avian-singles - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (let [details (assoc (:details (mt/db)) :schema-filters-type "inclusion" :schema-filters-patterns "metabase_cache*,20*,pg_*")] ; 20* matches test session schemas diff --git a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj index 3825b16a304e43d952837b52f24f9c6734f91db6..066c0c687ab53677a3e5e6280d8076282852c405 100644 --- a/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj +++ b/modules/drivers/snowflake/test/metabase/driver/snowflake_test.clj @@ -41,7 +41,7 @@ (set! *warn-on-reflection* true) -(defn- query->native [query] +(defn- query->native! [query] (let [check-sql-fn (fn [_ _ sql & _] (throw (ex-info "done" {::native-query sql})))] (with-redefs [sql-jdbc.execute/prepared-statement check-sql-fn @@ -702,7 +702,7 @@ "userId" 1000 "databaseId" (mt/id)} result-query (driver/prettify-native-form :snowflake - (query->native + (query->native! (assoc (mt/mbql-query users {:limit 2000}) :parameters [{:type "id" diff --git a/test/metabase/actions/execution_test.clj b/test/metabase/actions/execution_test.clj index 7e9ad8a89336693159f994fb7325898058bec761..84d2e086236ccb24431155e0fc173276c4ec8a1e 100644 --- a/test/metabase/actions/execution_test.clj +++ b/test/metabase/actions/execution_test.clj @@ -27,7 +27,7 @@ :cache-strategy nil)] (mt/with-actions [_ {:type :model :dataset_query dataset-query} {:keys [action-id]} {:type :implicit :kind "row/update"}] - (process-userland-query-test/with-query-execution [qe query] + (process-userland-query-test/with-query-execution! [qe query] (is (= {"id" 1 "name" "Red Medicine"} (actions.execution/fetch-values (action/select-action :id action-id) {"id" 1}))) (is (=? {:action_id action-id} diff --git a/test/metabase/actions/test_util.clj b/test/metabase/actions/test_util.clj index 20421ae06e94b980b4131f695b2badc1e9e8f111..b06d795b0dfaaf327c5903c9cc3cd4d5b993b139 100644 --- a/test/metabase/actions/test_util.clj +++ b/test/metabase/actions/test_util.clj @@ -237,6 +237,8 @@ options-map))] {:action-id action-id :model-id model-id})))) +;;; TODO FIXME -- rename this to [[with-actions!]] and then remove the Kondo ignore comment below +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-actions "Execute `body` with newly created Actions. `binding-forms-and-options-maps` is a vector of even number of elements, binding and options-map, @@ -310,6 +312,8 @@ [& body] `(do-with-actions-set! false (fn [] ~@body))) +;;; TODO FIXME -- rename this to [[with-actions!]] and then remove the Kondo ignore comment below +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-actions-test-data-and-actions-enabled "Combines [[with-actions-test-data]] and [[with-actions-enabled]]." {:style/indent 0} diff --git a/test/metabase/actions_test.clj b/test/metabase/actions_test.clj index 755a6c02b789ca2463d879a85756ba2d0105c14d..f151f621c31968b8792ef7511d0f127ff9a1df8b 100644 --- a/test/metabase/actions_test.clj +++ b/test/metabase/actions_test.clj @@ -19,7 +19,7 @@ (set! *warn-on-reflection* true) -(defmacro with-actions-test-data-and-actions-permissively-enabled +(defmacro with-actions-test-data-and-actions-permissively-enabled! "Combines [[mt/with-actions-test-data-and-actions-enabled]] with full permissions." {:style/indent 0} [& body] @@ -38,7 +38,7 @@ (deftest create-test (testing "row/create" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (let [response (actions/perform-action! :row/create (assoc (mt/mbql-query categories) :create-row {(format-field-name :name) "created_row"}))] (is (=? {:created-row {(format-field-name :id) 76 @@ -52,7 +52,7 @@ (deftest create-invalid-data-test (testing "row/create -- invalid data" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= 75 (categories-row-count))) (is (thrown-with-msg? Exception (case driver/*driver* @@ -72,7 +72,7 @@ (deftest update-test (testing "row/update" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= {:rows-updated [1]} (actions/perform-action! :row/update (assoc (mt/mbql-query categories {:filter [:= $id 50]}) @@ -85,7 +85,7 @@ (deftest delete-test (testing "row/delete" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= {:rows-deleted [1]} (actions/perform-action! :row/delete (mt/mbql-query categories {:filter [:= $id 50]}))) @@ -210,7 +210,7 @@ (testing "row/delete" (testing "should return error message if value cannot be parsed correctly for Field in question" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (thrown-with-msg? Exception #"Error filtering against :type/(Big)?Integer Field: unable to parse String \"one\" to a :type/(Big)?Integer" ;; TODO -- this really should be returning a 400 but we need to rework the code in ;; [[metabase.driver.sql-jdbc.actions]] a little to have that happen without changing other stuff @@ -227,7 +227,7 @@ (testing "FK constraint violations errors should have nice error messages (at least for Postgres) (#24021)" (mt/test-drivers (mt/normal-drivers-with-feature :actions) (mt/with-actions-test-data-tables #{"venues" "categories"} - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! ;; attempting to delete the `Pizza` category should fail because there are several rows in `venues` that have ;; this `category_id` -- it's an FK constraint violation. @@ -255,7 +255,7 @@ (deftest bulk-create-happy-path-test (testing "bulk/create" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= 75 (categories-row-count))) (is (= {:created-rows [{(format-field-name :id) 76, (format-field-name :name) "NEW_A"} @@ -275,7 +275,7 @@ (deftest bulk-create-failure-test (testing "bulk/create" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (testing "error in some of the rows in request body" (is (= 75 (categories-row-count))) @@ -311,7 +311,7 @@ (deftest bulk-delete-happy-path-test (testing "bulk/delete" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= 75 (categories-row-count))) (is (= {:success true} @@ -326,7 +326,7 @@ (deftest bulk-delete-failure-test (testing "bulk/delete" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (testing "error in some of the rows" (is (= 75 (categories-row-count))) @@ -389,7 +389,7 @@ (deftest bulk-update-happy-path-test (testing "bulk/update" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (is (= [[1 "African"] [2 "American"] [3 "Artisan"]] @@ -413,7 +413,7 @@ (deftest bulk-update-failure-test (testing "bulk/update" (mt/test-drivers (mt/normal-drivers-with-feature :actions) - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (let [id (format-field-name :id) name (format-field-name :name) update-categories! (fn [rows] @@ -497,7 +497,7 @@ (let [username "username", password "password"] (with-open [ssh-server (basic-auth-ssh-server username password)] (doseq [[correct-password? ssh-password] [[true password] [false "wrong-password"]]] - (with-actions-test-data-and-actions-permissively-enabled + (with-actions-test-data-and-actions-permissively-enabled! (let [ssh-port (.getPort ^SshServer ssh-server)] (let [details (t2/select-one-fn :details 'Database :id (mt/id))] (t2/update! 'Database (mt/id) diff --git a/test/metabase/analytics/prometheus_test.clj b/test/metabase/analytics/prometheus_test.clj index 52ff2f9e7277f5de05d7a1ac4867828427c9b566..9981879bffd75966a747426481c91b97959aa010 100644 --- a/test/metabase/analytics/prometheus_test.clj +++ b/test/metabase/analytics/prometheus_test.clj @@ -95,7 +95,7 @@ str/split-lines (remove #(str/starts-with? % "#")))) -(defmacro with-prometheus-system +(defmacro with-prometheus-system! "Run tests with a prometheus web server and registry. Provide binding symbols in a tuple of [port system]. Port will be bound to the random port used for the metrics endpoint and system will be a [[PrometheusSystem]] which has a registry and web-server." @@ -110,27 +110,27 @@ (deftest web-server-test (testing "Can get metrics from the web-server" - (with-prometheus-system [port _] + (with-prometheus-system! [port _] (let [metrics-in-registry (metric-tags port)] (is (seq (set/intersection common-metrics metrics-in-registry)) "Did not get metrics from the port")))) (testing "Throws helpful message if cannot start server" ;; start another system on the same port - (with-prometheus-system [port _] + (with-prometheus-system! [port _] (is (thrown-with-msg? clojure.lang.ExceptionInfo #"Failed to initialize Prometheus on port" (#'prometheus/make-prometheus-system port "test-failure")))))) (deftest c3p0-collector-test (testing "Registry has c3p0 registered" - (with-prometheus-system [_ system] + (with-prometheus-system! [_ system] (let [registry (.registry system) c3p0-collector (registry/get registry {:name "c3p0-stats" :namespace "metabase_database"} nil)] (is c3p0-collector "c3p0 stats not found")))) (testing "Registry has an entry for each database in [[prometheus/connection-pool-info]]" - (with-prometheus-system [_ system] + (with-prometheus-system! [_ system] (let [registry (.registry system) c3p0-collector (registry/get registry {:name "c3p0_stats" :namespace "metabase_database"} @@ -143,7 +143,7 @@ (count (.samples ^GaugeMetricFamily (first measurements)))) "Expected one entry per database for each measurement")))) (testing "Registry includes c3p0 stats" - (with-prometheus-system [port _] + (with-prometheus-system! [port _] (let [[db-name values] (first (prometheus/connection-pool-info)) tag-name (comp :label #'prometheus/label-translation) expected-lines (set (for [[tag value] values] @@ -156,7 +156,7 @@ (deftest email-collector-test (testing "Registry has email metrics registered" - (with-prometheus-system [port _] + (with-prometheus-system! [port _] (is (= #{"metabase_email_messages_total" "metabase_email_messages_created" "metabase_email_message_errors_total" "metabase_email_message_errors_created"} (->> (metric-lines port) (map #(str/split % #"\s+")) @@ -168,9 +168,9 @@ (testing "inc has no effect if system is not setup" (prometheus/inc :metabase-email/messages)) ; << Does not throw. (testing "inc has no effect when called with unknown metric" - (with-prometheus-system [_ _system] + (with-prometheus-system! [_ _system] (prometheus/inc :metabase-email/unknown-metric))) ; << Does not throw. (testing "inc is recorded for known metrics" - (with-prometheus-system [_ system] + (with-prometheus-system! [_ system] (prometheus/inc :metabase-email/messages) (is (< 0 (-> system :registry :metabase-email/messages ops/read-value)))))) diff --git a/test/metabase/analytics/snowplow_test.clj b/test/metabase/analytics/snowplow_test.clj index 5bebbd1ab57097cdf3294a28851d00a7b07c1ef9..1e0e9b275701af675a4f5c5d4afcef3f075f5ab8 100644 --- a/test/metabase/analytics/snowplow_test.clj +++ b/test/metabase/analytics/snowplow_test.clj @@ -45,7 +45,7 @@ context (normalize-map (.getMap context-json))] (swap! collector conj {:properties properties, :subject subject, :context context}))) -(defn do-with-fake-snowplow-collector +(defn do-with-fake-snowplow-collector! "Impl for `with-fake-snowplow-collector` macro; prefer using that rather than calling this directly." [f] (mt/with-temporary-setting-values [snowplow-available true @@ -55,7 +55,8 @@ (with-redefs [snowplow/track-event-impl! (partial fake-track-event-impl! collector)] (f)))))) -;;; TODO -- rename to `with-fake-snowplow-collector!` because this is not thread-safe. +;;; TODO -- rename to `with-fake-snowplow-collector!` because this is not thread-safe and remove the Kondo ignore rule below +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-fake-snowplow-collector "Creates a new fake snowplow collector in a dynamic scope, and redefines the track-event! function so that analytics events are parsed and added to the fake collector. @@ -63,7 +64,7 @@ Fetch the contents of the collector by calling [[snowplow-collector-contents]]." [& body] {:style/indent 0} - `(do-with-fake-snowplow-collector (fn [] ~@body))) + `(do-with-fake-snowplow-collector! (fn [] ~@body))) (defn- clear-snowplow-collector! [] diff --git a/test/metabase/api/alert_test.clj b/test/metabase/api/alert_test.clj index f176c48d13e183e92fd121c6fc2d96e6e9d20e3e..30cc052b8d004ffb30bcc86479e34f5dcbffeac2 100644 --- a/test/metabase/api/alert_test.clj +++ b/test/metabase/api/alert_test.clj @@ -63,20 +63,20 @@ (defn- alert-response [response] (m/dissoc-in response [:creator :last_login])) -(defmacro ^:private with-test-email [& body] +(defmacro ^:private with-test-email! [& body] `(mt/with-temporary-setting-values [~'site-url "https://metabase.com/testmb"] (mt/with-fake-inbox ~@body))) -(defmacro ^:private with-alert-setup +(defmacro ^:private with-alert-setup! "Macro that will cleanup any created pulses and setups a fake-inbox to validate emails are sent for new alerts" [& body] `(mt/with-model-cleanup [Pulse] - (with-test-email + (with-test-email! ~@body))) -(defn- do-with-alert-in-collection [f] - (pulse-test/with-pulse-in-collection [db collection alert card] +(defn- do-with-alert-in-collection! [f] + (pulse-test/with-pulse-in-collection! [db collection alert card] (assert (t2/exists? PulseCard :card_id (u/the-id card), :pulse_id (u/the-id alert))) ;; Make this Alert actually be an alert (t2/update! Pulse (u/the-id alert) {:alert_condition "rows"}) @@ -87,7 +87,7 @@ (let [card (t2/select-one Card :id (u/the-id card))] (f db collection alert card))))) -(defmacro ^:private with-alert-in-collection +(defmacro ^:private with-alert-in-collection! "Do `body` with a temporary Alert whose Card is in a Collection, setting the stage to write various tests below. (Make sure to grant All Users permissions to the Collection if needed.)" {:style/indent 1} @@ -97,12 +97,12 @@ (assert (not= alert-binding 'card))) (when card-binding (assert (not= card-binding 'alert))) - `(do-with-alert-in-collection + `(do-with-alert-in-collection! (fn [~(or db-binding '_) ~(or collection-binding '_) ~(or alert-binding '_) ~(or card-binding '_)] ~@body))) ;; This stuff below is separate from `with-alert-in-collection` above! -(defn- do-with-alerts-in-a-collection +(defn- do-with-alerts-in-a-collection! "Do `f` with the Cards associated with `alerts-or-ids` in a new temporary Collection. Grant perms to All Users to that Collection using `f`. @@ -119,11 +119,11 @@ (t2/update! Card (u/the-id card) {:collection_id (u/the-id collection)}))) (f)))) -(defmacro ^:private with-alerts-in-readable-collection [alerts-or-ids & body] - `(do-with-alerts-in-a-collection perms/grant-collection-read-permissions! ~alerts-or-ids (fn [] ~@body))) +(defmacro ^:private with-alerts-in-readable-collection! [alerts-or-ids & body] + `(do-with-alerts-in-a-collection! perms/grant-collection-read-permissions! ~alerts-or-ids (fn [] ~@body))) -(defmacro ^:private with-alerts-in-writeable-collection [alerts-or-ids & body] - `(do-with-alerts-in-a-collection perms/grant-collection-readwrite-permissions! ~alerts-or-ids (fn [] ~@body))) +(defmacro ^:private with-alerts-in-writeable-collection! [alerts-or-ids & body] + `(do-with-alerts-in-a-collection! perms/grant-collection-readwrite-permissions! ~alerts-or-ids (fn [] ~@body))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -149,28 +149,28 @@ (deftest get-alerts-test (testing "archived alerts should be excluded" (is (= #{"Not Archived"} - (with-alert-in-collection [_ _ not-archived-alert] - (with-alert-in-collection [_ _ archived-alert] + (with-alert-in-collection! [_ _ not-archived-alert] + (with-alert-in-collection! [_ _ archived-alert] (t2/update! Pulse (u/the-id not-archived-alert) {:name "Not Archived"}) (t2/update! Pulse (u/the-id archived-alert) {:name "Archived", :archived true}) - (with-alerts-in-readable-collection [not-archived-alert archived-alert] + (with-alerts-in-readable-collection! [not-archived-alert archived-alert] (set (map :name (mt/user-http-request :rasta :get 200 "alert"))))))))) (testing "fetch archived alerts" (is (= #{"Archived"} - (with-alert-in-collection [_ _ not-archived-alert] - (with-alert-in-collection [_ _ archived-alert] + (with-alert-in-collection! [_ _ not-archived-alert] + (with-alert-in-collection! [_ _ archived-alert] (t2/update! Pulse (u/the-id not-archived-alert) {:name "Not Archived"}) (t2/update! Pulse (u/the-id archived-alert) {:name "Archived", :archived true}) - (with-alerts-in-readable-collection [not-archived-alert archived-alert] + (with-alerts-in-readable-collection! [not-archived-alert archived-alert] (set (map :name (mt/user-http-request :rasta :get 200 "alert" :archived true))))))))) (testing "fetch alerts by user ID -- should return alerts created by the user, or alerts for which the user is a known recipient" - (with-alert-in-collection [_ _ creator-alert] - (with-alert-in-collection [_ _ recipient-alert] - (with-alert-in-collection [_ _ other-alert] - (with-alerts-in-readable-collection [creator-alert recipient-alert other-alert] + (with-alert-in-collection! [_ _ creator-alert] + (with-alert-in-collection! [_ _ recipient-alert] + (with-alert-in-collection! [_ _ other-alert] + (with-alerts-in-readable-collection! [creator-alert recipient-alert other-alert] (t2/update! Pulse (u/the-id creator-alert) {:name "LuckyCreator" :creator_id (mt/user->id :lucky)}) (t2/update! Pulse (u/the-id recipient-alert) {:name "LuckyRecipient"}) (t2/update! Pulse (u/the-id other-alert) {:name "Other"}) @@ -196,8 +196,8 @@ (deftest get-alert-test (testing "an alert can be fetched by ID" - (with-alert-in-collection [_ _ alert] - (with-alerts-in-readable-collection [alert] + (with-alert-in-collection! [_ _ alert] + (with-alerts-in-readable-collection! [alert] (is (= (u/the-id alert) (:id (mt/user-http-request :rasta :get 200 (str "alert/" (u/the-id alert))))))))) @@ -314,7 +314,7 @@ (mt/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection collection] (t2/update! Card (u/the-id card) {:collection_id (u/the-id collection)}) - (with-alert-setup + (with-alert-setup! (perms/grant-collection-read-permissions! (perms-group/all-users) collection) [(et/with-expected-messages 1 (alert-response @@ -350,7 +350,7 @@ {"My question" true "now getting alerts" true "confirmation that your alert" false}))} - (with-alert-setup + (with-alert-setup! (array-map :response (et/with-expected-messages 2 (-> ((alert-client :crowberto) :post 200 "alert" @@ -376,7 +376,7 @@ :display "line" :collection_id (u/the-id collection)}] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) - (with-alert-setup + (with-alert-setup! (et/with-expected-messages 1 (mt/user-http-request :rasta :post 200 "alert" @@ -398,7 +398,7 @@ :display "bar" :collection_id (u/the-id collection)}] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) - (with-alert-setup + (with-alert-setup! (et/with-expected-messages 1 (mt/user-http-request :rasta :post 200 "alert" @@ -529,7 +529,7 @@ (assoc-in [:channels 0 :recipients] (set (map recipient-details [:crowberto :rasta])))) (et/email-to :rasta {:subject "Crowberto Corv added you to an alert" :body {"https://metabase.com/testmb" true, "now getting alerts" true}})] - (with-alert-setup + (with-alert-setup! [(et/with-expected-messages 1 (alert-response (setify-recipient-emails @@ -548,7 +548,7 @@ PulseChannelRecipient _ (recipient pc :rasta)] (is (= (-> (default-alert card) (assoc-in [:card :collection_id] true)) - (with-alerts-in-writeable-collection [alert] + (with-alerts-in-writeable-collection! [alert] (mt/with-model-cleanup [Pulse] (alert-response ((alert-client :rasta) :put 200 (alert-url alert) @@ -562,7 +562,7 @@ PulseChannelRecipient _ (recipient pc :rasta)] (is (= (str "Non-admin users without monitoring or subscription permissions " "are not allowed to modify the channels for an alert") - (with-alerts-in-writeable-collection [alert] + (with-alerts-in-writeable-collection! [alert] (mt/with-model-cleanup [Pulse] ((alert-client :rasta) :put 403 (alert-url alert) (default-alert-req card pc {} [(mt/fetch-user :crowberto)]))))))))) @@ -575,7 +575,7 @@ PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :crowberto) PulseChannelRecipient _ (recipient pc :rasta)] - (with-alert-setup + (with-alert-setup! (testing "API response" (is (= (-> (default-alert card) (assoc-in [:channels 0 :recipients] [(recipient-details :crowberto)])) @@ -600,7 +600,7 @@ PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :rasta)] (mt/with-non-admin-groups-no-root-collection-perms - (with-alert-setup + (with-alert-setup! ((alert-client :rasta) :put 403 (alert-url alert) (default-alert-req card pc))))))))) @@ -612,13 +612,13 @@ PulseCard _ (pulse-card alert card) PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :crowberto)] - (with-alert-setup + (with-alert-setup! ((alert-client :rasta) :put 403 (alert-url alert) (default-alert-req card pc)))))))) (testing "Non-admin users can update alerts in collection they have view permisisons" (mt/with-non-admin-groups-no-root-collection-perms - (with-alert-in-collection [_ collection alert card] + (with-alert-in-collection! [_ collection alert card] (mt/with-temp [PulseCard pc (pulse-card alert card)] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) @@ -637,7 +637,7 @@ :display "bar" :collection_id (u/the-id collection)}] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) - (with-alert-setup + (with-alert-setup! (et/with-expected-messages 1 (let [alert-details {:card {:id (u/the-id card), :include_csv false, :include_xls false, :dashboard_card_id nil} :collection_id (u/the-id collection) @@ -709,9 +709,9 @@ (update-in [:channels 0] merge {:schedule_hour 15, :schedule_type "daily"}) (assoc-in [:card :collection_id] true))] (mt/with-non-admin-groups-no-root-collection-perms - (with-alert-setup + (with-alert-setup! (map alert-response - (with-alerts-in-readable-collection [alert] + (with-alerts-in-readable-collection! [alert] ((alert-client :rasta) :get 200 (alert-question-url card))))))))) (testing "Non-admin users shouldn't see alerts they created if they're no longer recipients" @@ -723,8 +723,8 @@ PulseChannel pc (pulse-channel alert) PulseChannelRecipient pcr (recipient pc :rasta) PulseChannelRecipient _ (recipient pc :crowberto)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (array-map :count-1 (count ((alert-client :rasta) :get 200 (alert-question-url card))) :count-2 (do @@ -748,8 +748,8 @@ PulseChannel pc-2 (pulse-channel alert-2) PulseChannelRecipient _ (recipient pc-2 :crowberto) PulseChannel _ (assoc (pulse-channel alert-2) :channel_type "slack")] - (with-alerts-in-readable-collection [alert-1 alert-2] - (with-alert-setup + (with-alerts-in-readable-collection! [alert-1 alert-2] + (with-alert-setup! (array-map :rasta (api:alert-question-count :rasta card) :crowberto (api:alert-question-count :crowberto card)))))))) @@ -771,8 +771,8 @@ PulseChannel pc-2 (pulse-channel alert-2) PulseChannelRecipient _ (recipient pc-2 :crowberto) PulseChannel _ (assoc (pulse-channel alert-2) :channel_type "slack")] - (with-alerts-in-readable-collection [alert-1 alert-2] - (with-alert-setup + (with-alerts-in-readable-collection! [alert-1 alert-2] + (with-alert-setup! (is (= {:rasta 0 :crowberto 0} (array-map @@ -814,8 +814,8 @@ PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :rasta) PulseChannelRecipient _ (recipient pc :crowberto)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (array-map :recipients-1 (recipient-emails (mt/user-http-request :rasta :get 200 (alert-question-url card))) @@ -837,8 +837,8 @@ PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :rasta) PulseChannelRecipient _ (recipient pc :crowberto)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (array-map :recipients-1 (recipient-emails (mt/user-http-request :rasta :get 200 (alert-question-url card))) :recipients-2 (do @@ -856,8 +856,8 @@ PulseCard _ (pulse-card alert card) PulseChannel pc (pulse-channel alert) PulseChannelRecipient _ (recipient pc :rasta)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (et/with-expected-messages 1 (api:unsubscribe! :rasta 204 alert)) (array-map :archived? (t2/select-one-fn :archived Pulse :id (u/the-id alert)) @@ -873,8 +873,8 @@ PulseChannel pc-1 (assoc (pulse-channel alert) :channel_type :email) PulseChannel _ (assoc (pulse-channel alert) :channel_type :slack) PulseChannelRecipient _ (recipient pc-1 :rasta)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (et/with-expected-messages 1 (api:unsubscribe! :rasta 204 alert)) (array-map :archived? (t2/select-one-fn :archived Pulse :id (u/the-id alert)) @@ -892,8 +892,8 @@ PulseChannel pc-1 (assoc (pulse-channel alert) :channel_type :email) PulseChannel _pc-2 (assoc (pulse-channel alert) :channel_type :slack) PulseChannelRecipient _ (recipient pc-1 :rasta)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (et/with-expected-messages 1 ((alert-client :crowberto) :put 200 (alert-url alert) (assoc-in (default-alert-req card pc-1) [:channels 0 :enabled] false))) @@ -913,8 +913,8 @@ PulseChannel pc-1 (assoc (pulse-channel alert) :channel_type :email, :enabled false) PulseChannel _pc-2 (assoc (pulse-channel alert) :channel_type :slack) PulseChannelRecipient _ (recipient pc-1 :rasta)] - (with-alerts-in-readable-collection [alert] - (with-alert-setup + (with-alerts-in-readable-collection! [alert] + (with-alert-setup! (et/with-expected-messages 1 ((alert-client :crowberto) :put 200 (alert-url alert) (assoc-in (default-alert-req card pc-1) [:channels 0 :enabled] true))) diff --git a/test/metabase/api/automagic_dashboards_test.clj b/test/metabase/api/automagic_dashboards_test.clj index 00dbe2ac3c90fdc75a849d9e26ac0bf85e3bea18..634b61df43d00141823db5d82b9962ecbcc8f760 100644 --- a/test/metabase/api/automagic_dashboards_test.clj +++ b/test/metabase/api/automagic_dashboards_test.clj @@ -288,7 +288,7 @@ {:col 10 :size_x 10}])] (is (= 20 size_x))))) -(defn- do-with-testing-model +(defn- do-with-indexed-model! [{:keys [query pk-ref value-ref]} f] (t2.with-temp/with-temp [Card model {:type :model :dataset_query query}] @@ -304,7 +304,7 @@ :model_index_id (:id model-index) :model_pk 1)}))))) -(defmacro with-indexed-model +(defmacro with-indexed-model! "Creates a model based on `query-info`, which is indexed. `query-info` is a map with keys: @@ -312,8 +312,8 @@ - pk-ref: a field_ref for the model's pk - value-ref: a field_ref for the model's label." [[bindings query-info] & body] - `(do-with-testing-model ~query-info - (fn [~bindings] ~@body))) + `(do-with-indexed-model! ~query-info + (fn [~bindings] ~@body))) (def Tab-Id-Schema "Schema for tab-ids. Must be integers for the front-end, but negative so we know they do not (yet) exist in the db." @@ -356,10 +356,10 @@ (deftest create-linked-dashboard-test-regular-queries (mt/dataset test-data (testing "x-ray an mbql model" - (with-indexed-model [{:keys [model model-index model-index-value]} - {:query (mt/mbql-query products) - :pk-ref (mt/$ids :products $id) - :value-ref (mt/$ids :products $title)}] + (with-indexed-model! [{:keys [model model-index model-index-value]} + {:query (mt/mbql-query products) + :pk-ref (mt/$ids :products $id) + :value-ref (mt/$ids :products $title)}] (let [dash (#'api.magic/create-linked-dashboard {:model model :model-index model-index @@ -416,10 +416,10 @@ id-field-ref (:field_ref (by-id results-meta "id")) title-field-ref (:field_ref (by-id results-meta "title")) id-field-id (mt/id :products :id)] - (with-indexed-model [{:keys [model model-index model-index-value]} - {:query (mt/native-query {:query "select * from products"}) - :pk-ref id-field-ref - :value-ref title-field-ref}] + (with-indexed-model! [{:keys [model model-index model-index-value]} + {:query (mt/native-query {:query "select * from products"}) + :pk-ref id-field-ref + :value-ref title-field-ref}] ;; need user metadata edits to find linked tables to an otherwise opaque native query (t2/update! :model/Card (:id model) {:result_metadata (annotating results-meta id-field-ref @@ -452,7 +452,7 @@ (deftest create-linked-dashboard-test-single-link (mt/dataset test-data (testing "with only single linked table" - (with-indexed-model [{:keys [model model-index model-index-value]} + (with-indexed-model! [{:keys [model model-index model-index-value]} {:query (mt/mbql-query people) :pk-ref (mt/$ids :people $id) :value-ref (mt/$ids :people $email)}] diff --git a/test/metabase/api/card_test.clj b/test/metabase/api/card_test.clj index 74223f77e3b64172f23fbbdae8cd6697579cd92b..f20f6b0ba1d810ca952bcb7a8118a643ede6397a 100644 --- a/test/metabase/api/card_test.clj +++ b/test/metabase/api/card_test.clj @@ -140,7 +140,7 @@ `(do-with-temp-native-card! (fn [~(or db-binding '_) ~(or card-binding '_)] ~@body))) -(defn do-with-cards-in-a-collection [card-or-cards-or-ids grant-perms-fn! f] +(defn do-with-cards-in-a-collection! [card-or-cards-or-ids grant-perms-fn! f] (mt/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection collection] ;; put all the Card(s) in our temp `collection` @@ -153,18 +153,18 @@ ;; call (f) (f)))) -(defmacro with-cards-in-readable-collection +(defmacro with-cards-in-readable-collection! "Execute `body` with `card-or-cards-or-ids` added to a temporary Collection that All Users have read permissions for." {:style/indent 1} [card-or-cards-or-ids & body] - `(do-with-cards-in-a-collection ~card-or-cards-or-ids perms/grant-collection-read-permissions! (fn [] ~@body))) + `(do-with-cards-in-a-collection! ~card-or-cards-or-ids perms/grant-collection-read-permissions! (fn [] ~@body))) -(defmacro with-cards-in-writeable-collection +(defmacro with-cards-in-writeable-collection! "Execute `body` with `card-or-cards-or-ids` added to a temporary Collection that All Users have *write* permissions for." {:style/indent 1} [card-or-cards-or-ids & body] - `(do-with-cards-in-a-collection ~card-or-cards-or-ids perms/grant-collection-readwrite-permissions! (fn [] ~@body))) + `(do-with-cards-in-a-collection! ~card-or-cards-or-ids perms/grant-collection-readwrite-permissions! (fn [] ~@body))) (defn- do-with-temp-native-card-with-params! [f] (mt/with-temp @@ -249,7 +249,7 @@ (mt/with-temp [Database db {} :model/Card card-1 {:database_id (mt/id)} :model/Card card-2 {:database_id (u/the-id db)}] - (with-cards-in-readable-collection [card-1 card-2] + (with-cards-in-readable-collection! [card-1 card-2] (is (= true (card-returned? :database (mt/id) card-1))) (is (= false @@ -272,7 +272,7 @@ Table table-2 {:db_id (u/the-id db)} :model/Card card-1 {:table_id (u/the-id table-1)} :model/Card card-2 {:table_id (u/the-id table-2)}] - (with-cards-in-readable-collection [card-1 card-2] + (with-cards-in-readable-collection! [card-1 card-2] (is (= true (card-returned? :table (u/the-id table-1) (u/the-id card-1)))) (is (= false @@ -290,7 +290,7 @@ (mt/with-temp [:model/Card card-1 {:name "Card 1"} :model/Card card-2 {:name "Card 2"} :model/Card card-3 {:name "Card 3"}] - (with-cards-in-readable-collection [card-1 card-2 card-3] + (with-cards-in-readable-collection! [card-1 card-2 card-3] (mt/user-http-request :crowberto :put 200 (format "card/%d" (u/the-id card-2)) {:archived true}) (mt/user-http-request :crowberto :put 200 (format "card/%d" (u/the-id card-3)) {:archived true}) (is (= #{"Card 2" "Card 3"} @@ -354,7 +354,7 @@ :model/Card card-3 {:name "Card 3"} CardBookmark _ {:card_id (u/the-id card-1), :user_id (mt/user->id :rasta)} CardBookmark _ {:card_id (u/the-id card-2), :user_id (mt/user->id :crowberto)}] - (with-cards-in-readable-collection [card-1 card-2 card-3] + (with-cards-in-readable-collection! [card-1 card-2 card-3] (is (= [{:name "Card 1"}] (for [card (mt/user-http-request :rasta :get 200 "card", :f :bookmarked)] (select-keys card [:name])))))))) @@ -444,7 +444,7 @@ :archived true :dataset_query {:query {:source-table (str "card__" model-id)}}}] (testing "list cards using a model" - (with-cards-in-readable-collection [model card-1 card-2 card-3 card-4 card-5 card-6 card-7] + (with-cards-in-readable-collection! [model card-1 card-2 card-3 card-4 card-5 card-6 card-7] (is (= #{"Card 1" "Card 3" "Card 4"} (into #{} (map :name) (mt/user-http-request :rasta :get 200 "card" :f :using_model :model_id model-id)))) @@ -458,7 +458,7 @@ (deftest get-cards-with-last-edit-info-test (mt/with-temp [:model/Card {card-1-id :id} {:name "Card 1"} :model/Card {card-2-id :id} {:name "Card 2"}] - (with-cards-in-readable-collection [card-1-id card-2-id] + (with-cards-in-readable-collection! [card-1-id card-2-id] (doseq [user-id [(mt/user->id :rasta) (mt/user->id :crowberto)]] (revision/push-revision! {:entity :model/Card @@ -551,7 +551,7 @@ {:name "A Native query table" :display :table :query_type "native"})] - (with-cards-in-readable-collection [line bar area scalar scalar-2 native pie table native-2 metric metric-2] + (with-cards-in-readable-collection! [line bar area scalar scalar-2 native pie table native-2 metric metric-2] (doseq [:let [excluded #{"A Scalar 2" "Another Metric" "A Line with no access" "A pie" "A table" "A Native query table"}] [card-id display-type expected excluded] [[(:id line) :line #{"A Native query" "An Area" "A Bar" "A Metric"} excluded] @@ -1316,7 +1316,7 @@ (deftest test-that-we-can-edit-a-card (t2.with-temp/with-temp [:model/Card card {:name "Original Name"}] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (is (= "Original Name" (t2/select-one-fn :name :model/Card, :id (u/the-id card)))) (is (= {:timestamp true, :first_name "Rasta", :last_name "Toucan", :email "rasta@metabase.com", :id true} @@ -1328,7 +1328,7 @@ (deftest can-we-update-a-card-s-archived-status- (t2.with-temp/with-temp [:model/Card card] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (let [archived? (fn [] (:archived (t2/select-one :model/Card :id (u/the-id card)))) set-archived! (fn [archived] (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:archived archived}) @@ -1359,13 +1359,13 @@ (deftest clear-description-test (testing "Can we clear the description of a Card? (#4738)" (t2.with-temp/with-temp [:model/Card card {:description "What a nice Card"}] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:description nil}) (is (nil? (t2/select-one-fn :description :model/Card :id (u/the-id card)))))))) (deftest description-should-be-blankable-as-well (t2.with-temp/with-temp [:model/Card card {:description "What a nice Card"}] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:description ""}) (is (= "" (t2/select-one-fn :description :model/Card :id (u/the-id card))))))) @@ -1437,7 +1437,7 @@ (deftest can-we-change-the-collection-position-of-a-card- (t2.with-temp/with-temp [:model/Card card] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:collection_position 1}) (is (= 1 @@ -1445,7 +1445,7 @@ (deftest can-we-change-the-collection-preview-flag-of-a-card- (t2.with-temp/with-temp [:model/Card card] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:collection_preview false}) (is (= false @@ -1453,7 +1453,7 @@ (deftest ---and-unset--unpin--it-as-well- (t2.with-temp/with-temp [:model/Card card {:collection_position 1}] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :put 200 (str "card/" (u/the-id card)) {:collection_position nil}) (is (= nil @@ -1850,7 +1850,7 @@ PulseChannelRecipient _ {:user_id (mt/user->id :rasta) :pulse_channel_id (u/the-id pc)}] (mt/with-temporary-setting-values [site-url "https://metabase.com"] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/with-fake-inbox (when deleted? (u/with-timeout 5000 @@ -1883,7 +1883,7 @@ PulseChannel pc {:pulse_id (u/the-id pulse)} PulseChannelRecipient _ {:user_id (mt/user->id :rasta) :pulse_channel_id (u/the-id pc)}] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/with-fake-inbox (array-map :emails-1 (do @@ -1902,7 +1902,7 @@ (deftest check-that-we-can-delete-a-card (is (nil? (t2.with-temp/with-temp [:model/Card card] - (with-cards-in-writeable-collection card + (with-cards-in-writeable-collection! card (mt/user-http-request :rasta :delete 204 (str "card/" (u/the-id card))) (t2/select-one :model/Card :id (u/the-id card))))))) @@ -2044,7 +2044,7 @@ (deftest csv-download-test (testing "no parameters" (with-temp-native-card! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= ["COUNT(*)" "75"] (str/split-lines @@ -2052,7 +2052,7 @@ (u/the-id card))))))))) (testing "with parameters" (with-temp-native-card-with-params! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= ["COUNT(*)" "8"] (str/split-lines @@ -2063,12 +2063,12 @@ (deftest json-download-test (testing "no parameters" (with-temp-native-card! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= [{(keyword "COUNT(*)") "75"}] (mt/user-http-request :rasta :post 200 (format "card/%d/query/json" (u/the-id card)))))))) (testing "with parameters" (with-temp-native-card-with-params! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= [{(keyword "COUNT(*)") "8"}] (mt/user-http-request :rasta :post 200 (format "card/%d/query/json" (u/the-id card)) :parameters encoded-params))))))) @@ -2168,13 +2168,13 @@ (deftest xlsx-download-test (testing "no parameters" (with-temp-native-card! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= [{:col "COUNT(*)"} {:col 75.0}] (parse-xlsx-results (mt/user-http-request :rasta :post 200 (format "card/%d/query/xlsx" (u/the-id card))))))))) (testing "with parameters" (with-temp-native-card-with-params! [_ card] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (is (= [{:col "COUNT(*)"} {:col 8.0}] (parse-xlsx-results (mt/user-http-request :rasta :post 200 (format "card/%d/query/xlsx" (u/the-id card)) @@ -2422,7 +2422,7 @@ :query {:source-table (mt/id :venues)} :middleware {:add-default-userland-constraints? true :userland-query? true}}}] - (with-cards-in-readable-collection card + (with-cards-in-readable-collection! card (let [orig qp.card/process-query-for-card] (with-redefs [qp.card/process-query-for-card (fn [card-id export-format & options] (apply orig card-id export-format @@ -3029,8 +3029,8 @@ :data :cols last :visibility_type)) "in cols (important for the saved metadata)")))))) -(defn- do-with-persistence-setup [f] - ;; mt/with-temp-scheduler actually just reuses the current scheduler. The scheduler factory caches by name set in +(defn- do-with-persistence-setup! [f] + ;; mt/with-temp-scheduler! actually just reuses the current scheduler. The scheduler factory caches by name set in ;; the resources/quartz.properties file and we reuse that scheduler (let [sched (.getScheduler (StdSchedulerFactory. (doto (java.util.Properties.) @@ -3052,11 +3052,11 @@ (finally (qs/shutdown sched)))))) -(defmacro ^:private with-persistence-setup +(defmacro ^:private with-persistence-setup! "Sets up a temp scheduler, a temp database and enabled persistence. Scheduler will be in standby mode so that jobs won't run. Just check for trigger presence." [db-binding & body] - `(do-with-persistence-setup (fn [~db-binding] ~@body))) + `(do-with-persistence-setup! (fn [~db-binding] ~@body))) (defn- job-info-for-individual-refresh "Return a set of PersistedInfo ids of all jobs scheduled for individual refreshes." @@ -3071,7 +3071,7 @@ (deftest refresh-persistence (testing "Can schedule refreshes for models" - (with-persistence-setup db + (with-persistence-setup! db (t2.with-temp/with-temp [:model/Card model {:type :model :database_id (u/the-id db)} :model/Card notmodel {:type :question :database_id (u/the-id db)} @@ -3096,7 +3096,7 @@ "Scheduled refresh of archived model")))))) (deftest unpersist-persist-model-test - (with-persistence-setup db + (with-persistence-setup! db (t2.with-temp/with-temp [:model/Card model {:database_id (u/the-id db), :type :model} :model/PersistedInfo pmodel {:database_id (u/the-id db), :card_id (u/the-id model)}] @@ -3443,7 +3443,7 @@ :columns [], :values [[:aggregation 0]]}, :table.cell_column "sum"}}] - (with-cards-in-readable-collection [model card] + (with-cards-in-readable-collection! [model card] (is (=? {:data {:cols [{:name "USER_ID"} {:name "pivot-grouping"} {:name "sum"}]}} (mt/user-http-request :rasta :post 202 (format "card/pivot/%d/query" (u/the-id card)))))))) @@ -3461,7 +3461,7 @@ :columns [], :values [[:aggregation 0]]}, :table.cell_column "sum"}}] - (with-cards-in-readable-collection [model card] + (with-cards-in-readable-collection! [model card] (is (=? {:data {:cols [{:name "USER_ID"} {:name "pivot-grouping"} {:name "sum"}]}} (mt/user-http-request :rasta :post 202 (format "card/pivot/%d/query" (u/the-id card))))))))))) diff --git a/test/metabase/api/collection_test.clj b/test/metabase/api/collection_test.clj index 5bf4de564a9354a41aa5154461e140764f0a64d0..5a76de5fd70eaaf61477dc777795331567a16c47 100644 --- a/test/metabase/api/collection_test.clj +++ b/test/metabase/api/collection_test.clj @@ -31,32 +31,32 @@ (use-fixtures :once (fixtures/initialize :test-users-personal-collections)) -(defmacro ^:private with-collection-hierarchy +(defmacro ^:private with-collection-hierarchy! "Totally-rad macro that creates a Collection hierarchy and grants the All Users group perms for all the Collections you've bound. See docs for [[metabase.models.collection-test/with-collection-hierarchy]] for more details." {:style/indent 1} [collection-bindings & body] {:pre [(vector? collection-bindings) (every? symbol? collection-bindings)]} - `(collection-test/with-collection-hierarchy [{:keys ~collection-bindings}] + `(collection-test/with-collection-hierarchy! [{:keys ~collection-bindings}] ~@(for [collection-symb collection-bindings] `(perms/grant-collection-read-permissions! (perms-group/all-users) ~collection-symb)) ~@body)) -(defn- do-with-french-user-and-personal-collection [f] +(defn- do-with-french-user-and-personal-collection! [f] (binding [collection/*allow-deleting-personal-collections* true] - (mt/with-mock-i18n-bundles {"fr" {:messages {"{0} {1}''s Personal Collection" "Collection personnelle de {0} {1}"}}} + (mt/with-mock-i18n-bundles! {"fr" {:messages {"{0} {1}''s Personal Collection" "Collection personnelle de {0} {1}"}}} (t2.with-temp/with-temp [User user {:locale "fr" :first_name "Taco" :last_name "Bell"} Collection collection {:personal_owner_id (:id user)}] (f user collection))))) -(defmacro ^:private with-french-user-and-personal-collection +(defmacro ^:private with-french-user-and-personal-collection! "Create a user with locale's fr and a collection associated with it" {:style/indent 2} [user collection & body] - `(do-with-french-user-and-personal-collection + `(do-with-french-user-and-personal-collection! (fn [~user ~collection] ~@body))) @@ -109,7 +109,7 @@ (deftest list-collections-personal-collection-locale-test (testing "GET /api/collection" (testing "Personal Collection's name and slug should be returned in user's locale" - (with-french-user-and-personal-collection user _collection + (with-french-user-and-personal-collection! user _collection (is (= [{:name "Collection personnelle de Taco Bell" :slug "collection_personnelle_de_taco_bell"}] (->> (mt/user-http-request user :get 200 "collection") @@ -233,7 +233,7 @@ (let [personal-collection (collection/user->personal-collection (mt/user->id :rasta))] (testing "sanity check" (is (some? personal-collection))) - (with-collection-hierarchy [a b c d e f g] + (with-collection-hierarchy! [a b c d e f g] (let [ids (set (map :id (cons personal-collection [a b c d e f g]))) response (mt/user-http-request :rasta :get 200 "collection/tree")] (testing "Make sure overall tree shape of the response is as is expected" @@ -264,7 +264,7 @@ (deftest collections-tree-exclude-other-user-collections-test (let [personal-collection (collection/user->personal-collection (mt/user->id :lucky))] - (with-collection-hierarchy [a b c d e f g] + (with-collection-hierarchy! [a b c d e f g] (collection/move-collection! a (collection/children-location personal-collection)) (let [ids (set (map :id (cons personal-collection [a b c d e f g]))) response-rasta (mt/user-http-request :rasta :get 200 "collection/tree" :exclude-other-user-collections true) @@ -290,7 +290,7 @@ (deftest collection-tree-here-and-below-test (testing "Tree should properly indicate contents" - (with-collection-hierarchy [a b] + (with-collection-hierarchy! [a b] (let [personal-collection (collection/user->personal-collection (mt/user->id :rasta))] (t2.with-temp/with-temp [Card _ {:name "Personal Card" :collection_preview false @@ -315,7 +315,7 @@ (deftest collection-tree-shallow-test (testing "GET /api/collection/tree?shallow=true" - (with-collection-hierarchy [a b c d e f g] + (with-collection-hierarchy! [a b c d e f g] (let [personal-collection (collection/user->personal-collection (mt/user->id :rasta)) ids (set (map :id (cons personal-collection [a b c d e f g])))] (let [response (mt/user-http-request :rasta :get 200 "collection/tree?shallow=true")] @@ -353,7 +353,7 @@ (deftest select-collections-shallow-test (testing "Selecting collections based off collection-id equaling nil works." - (with-collection-hierarchy [a b c d e f g] + (with-collection-hierarchy! [a b c d e f g] (let [personal-collection (collection/user->personal-collection (mt/user->id :crowberto)) ids (set (map :id (cons personal-collection [a b c d e f g])))] (mt/with-test-user :crowberto @@ -441,7 +441,7 @@ (deftest collection-tree-user-locale-test (testing "GET /api/collection/tree" (testing "for personal collections, it should return name and slug in user's locale" - (with-french-user-and-personal-collection user collection + (with-french-user-and-personal-collection! user collection (is (partial= {:description nil :archived false :entity_id (:entity_id collection) @@ -515,7 +515,7 @@ ;; A* -+-> E* ;; | ;; +-> F* -> G* - (collection-test/with-collection-hierarchy [{:keys [a b e f g], :as collections}] + (collection-test/with-collection-hierarchy! [{:keys [a b e f g], :as collections}] (doseq [collection [a b e f g]] (perms/grant-collection-read-permissions! (perms-group/all-users) collection)) (is (= [{:name "A" @@ -549,14 +549,14 @@ (mt/user-http-request :rasta :get 403 (str "collection/" (u/the-id collection)))))))) (testing "for personal collections, it should return name and slug in user's locale" - (with-french-user-and-personal-collection user collection + (with-french-user-and-personal-collection! user collection (is (=? {:name "Collection personnelle de Taco Bell" :slug "collection_personnelle_de_taco_bell"} (mt/user-http-request (:id user) :get 200 (str "collection/" (:id collection))))))))) ;;; ------------------------------------------------ Collection Items ------------------------------------------------ -(defn- do-with-some-children-of-collection [collection-or-id-or-nil f] +(defn- do-with-some-children-of-collection! [collection-or-id-or-nil f] (mt/with-non-admin-groups-no-root-collection-perms (let [collection-id-or-nil (when collection-or-id-or-nil (u/the-id collection-or-id-or-nil))] @@ -581,8 +581,8 @@ :dashboard-subscription-pulse-id dashboard-sub-pulse-id :dashboard-sub-pulse-card-id dashboard-sub-pulse-card-id}))))) -(defmacro ^:private with-some-children-of-collection {:style/indent 1} [collection-or-id-or-nil & body] - `(do-with-some-children-of-collection +(defmacro ^:private with-some-children-of-collection! {:style/indent 1} [collection-or-id-or-nil & body] + `(do-with-some-children-of-collection! ~collection-or-id-or-nil (fn [~'&ids] ~@body))) @@ -740,7 +740,7 @@ (testing "check that you get to see the children as appropriate" (t2.with-temp/with-temp [Collection collection {:name "Debt Collection"}] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) - (with-some-children-of-collection collection + (with-some-children-of-collection! collection (is (partial= (-> (mapv default-item [{:name "Acme Products", :model "pulse", :entity_id true} {:name "Birthday Card", :description nil, :model "card", :collection_preview false, :display "table", :entity_id true} @@ -753,7 +753,7 @@ (testing "...and that you can also filter so that you only see the children you want to see" (t2.with-temp/with-temp [Collection collection {:name "Art Collection"}] (perms/grant-collection-read-permissions! (perms-group/all-users) collection) - (with-some-children-of-collection collection + (with-some-children-of-collection! collection (is (partial= () (mt/boolean-ids-and-timestamps (:data (mt/user-http-request :rasta :get 200 (str "collection/" (u/the-id collection) "/items") :models "no_models"))))) @@ -1334,7 +1334,7 @@ (deftest effective-ancestors-and-children-test (testing "does a top-level Collection like A have the correct Children?" - (with-collection-hierarchy [a b c d g] + (with-collection-hierarchy! [a b c d g] (testing "ancestors" (is (= {:effective_ancestors [] :effective_location "/"} @@ -1345,7 +1345,7 @@ (deftest effective-ancestors-and-children-second-level-collection-test (testing "does a second-level Collection have its parent and its children?" - (with-collection-hierarchy [a b c d g] + (with-collection-hierarchy! [a b c d g] (testing "ancestors" (is (= {:effective_ancestors [{:name "A", :type nil, :id true, :can_write false, :personal_owner_id nil}] :effective_location "/A/"} @@ -1356,7 +1356,7 @@ (deftest effective-ancestors-and-children-third-level-collection-test (testing "Does a third-level Collection? have its parent and its children?" - (with-collection-hierarchy [a b c d g] + (with-collection-hierarchy! [a b c d g] (testing "ancestors" (is (= {:effective_ancestors [{:name "A", :type nil, :id true, :can_write false, :personal_owner_id nil} {:name "C", :type nil, :id true, :can_write false, :personal_owner_id nil}] @@ -1369,7 +1369,7 @@ (deftest effective-ancestors-and-children-of-d-test (testing (str "for D: if we remove perms for C we should only have A as an ancestor; effective_location should lie " "and say we are a child of A") - (with-collection-hierarchy [a b d g] + (with-collection-hierarchy! [a b d g] (testing "ancestors" (is (= {:effective_ancestors [{:name "A", :type nil, :id true, :can_write false, :personal_owner_id nil}] :effective_location "/A/"} @@ -1378,7 +1378,7 @@ (is (= [] (api-get-collection-children d)))))) (testing "for D: If, on the other hand, we remove A, we should see C as the only ancestor and as a root-level Collection." - (with-collection-hierarchy [b c d g] + (with-collection-hierarchy! [b c d g] (testing "ancestors" (is (= {:effective_ancestors [{:name "C", :type nil, :id true, :can_write false, :personal_owner_id nil}] :effective_location "/C/"} @@ -1389,7 +1389,7 @@ (deftest effective-ancestors-and-children-of-c-test (testing "for C: if we remove D we should get E and F as effective children" - (with-collection-hierarchy [a b c e f g] + (with-collection-hierarchy! [a b c e f g] (testing "ancestors" (is (= {:effective_ancestors [{:name "A", :type nil, :id true, :can_write false, :personal_owner_id nil}] :effective_location "/A/"} @@ -1400,7 +1400,7 @@ (deftest effective-ancestors-and-children-collapse-multiple-generations-test (testing "Make sure we can collapse multiple generations. For A: removing C and D should move up E and F" - (with-collection-hierarchy [a b e f g] + (with-collection-hierarchy! [a b e f g] (testing "ancestors" (is (= {:effective_ancestors [] :effective_location "/"} @@ -1411,7 +1411,7 @@ (deftest effective-ancestors-and-children-archived-test (testing "Let's make sure the 'archived` option works on Collections, nested or not" - (with-collection-hierarchy [a b c] + (with-collection-hierarchy! [a b c] (mt/user-http-request :crowberto :put 200 (str "collection/" (u/the-id b)) {:archived true}) (testing "ancestors" @@ -1458,7 +1458,7 @@ :parent_id nil :is_personal false :can_delete false} - (with-some-children-of-collection nil + (with-some-children-of-collection! nil (mt/user-http-request :crowberto :get 200 "collection/root"))))))) (defn results-matching [collection-items parameters] @@ -1476,7 +1476,7 @@ (assoc :fully_parameterized true)) (default-item {:name "Dine & Dashboard", :description nil, :model "dashboard"}) (default-item {:name "Electro-Magnetic Pulse", :model "pulse"})] - (with-some-children-of-collection nil + (with-some-children-of-collection! nil (-> (:data (mt/user-http-request :crowberto :get 200 "collection/root/items")) (remove-non-test-items &ids) remove-non-personal-collections @@ -1484,7 +1484,7 @@ (deftest fetch-root-items-limit-and-offset-test (testing "GET /api/collection/root/items" - (with-some-children-of-collection nil + (with-some-children-of-collection! nil (letfn [(items [limit offset] (:data (mt/user-http-request :crowberto :get 200 "collection/root/items" :limit (str limit), :offset (str offset))))] @@ -1500,7 +1500,7 @@ (deftest fetch-root-items-total-test (testing "GET /api/collection/root/items" (testing "Include :total, even with limit and offset" - (with-some-children-of-collection nil + (with-some-children-of-collection! nil ;; `:total` should be at least 4 items based on `with-some-children-of-collection`. Might be a bit more if ;; other stuff was created (is (<= 4 (:total (mt/user-http-request :crowberto :get 200 "collection/root/items" :limit "2" :offset "1")))))))) @@ -1510,12 +1510,12 @@ (testing "we don't let you see stuff you wouldn't otherwise be allowed to see" (is (= [] ;; if a User doesn't have perms for the Root Collection then they don't get to see things with no collection_id - (with-some-children-of-collection nil + (with-some-children-of-collection! nil (-> (:data (mt/user-http-request :rasta :get 200 "collection/root/items")) remove-non-personal-collections mt/boolean-ids-and-timestamps)))) (testing "...but if they have read perms for the Root Collection they should get to see them" - (with-some-children-of-collection nil + (with-some-children-of-collection! nil (t2.with-temp/with-temp [PermissionsGroup group {} PermissionsGroupMembership _ {:user_id (mt/user->id :rasta), :group_id (u/the-id group)}] (perms/grant-permissions! group (perms/collection-read-path {:metabase.models.collection.root/is-root? true})) @@ -1695,23 +1695,23 @@ (is (collection/user->personal-collection (mt/user->id :rasta)))) (testing "GET /api/collection/root/items" (testing "Do top-level collections show up as children of the Root Collection?" - (with-collection-hierarchy [a b c d e f g] + (with-collection-hierarchy! [a b c d e f g] (testing "children" (is (partial= (map collection-item ["A"]) (remove-non-test-collections (api-get-root-collection-children))))))) (testing "...and collapsing children should work for the Root Collection as well" - (with-collection-hierarchy [b d e f g] + (with-collection-hierarchy! [b d e f g] (testing "children" (is (partial= (map collection-item ["B" "D" "F"]) (remove-non-test-collections (api-get-root-collection-children))))))) (testing "does `archived` work on Collections as well?" - (with-collection-hierarchy [a b d e f g] + (with-collection-hierarchy! [a b d e f g] (mt/user-http-request :crowberto :put 200 (str "collection/" (u/the-id a)) {:archived true}) (is (= [] (remove-non-test-collections (api-get-root-collection-children))))) - (with-collection-hierarchy [a b d e f g] + (with-collection-hierarchy! [a b d e f g] (mt/user-http-request :crowberto :put 200 (str "collection/" (u/the-id a)) {:archived true}) (is (= [] (remove-non-test-collections (api-get-root-collection-children)))))) @@ -1830,7 +1830,7 @@ (testing "POST /api/collection" (testing "\nCan I create a Collection as a child of an existing collection?" (mt/with-model-cleanup [Collection] - (with-collection-hierarchy [a c d] + (with-collection-hierarchy! [a c d] (is (partial= (merge (mt/object-defaults Collection) {:id true @@ -1946,7 +1946,7 @@ (deftest move-collection-test (testing "PUT /api/collection/:id" (testing "Can I *change* the `location` of a Collection? (i.e. move it into a different parent Collection)" - (with-collection-hierarchy [a b e] + (with-collection-hierarchy! [a b e] (is (partial= (merge (mt/object-defaults Collection) {:id true diff --git a/test/metabase/api/common/internal_test.clj b/test/metabase/api/common/internal_test.clj index 35eafdb524035f0db1a49f0cf95a377d2bdccb8e..5e99d706f5fc2e3a8752674190580470a82f86b1 100644 --- a/test/metabase/api/common/internal_test.clj +++ b/test/metabase/api/common/internal_test.clj @@ -209,7 +209,7 @@ (:body (post! "/post/closed-test-address" {:id "1" :tags [] :a 1 :b 2})))) (testing "malli schema message are localized" - (mt/with-mock-i18n-bundles {"es" {:messages + (mt/with-mock-i18n-bundles! {"es" {:messages {"value must be a non-blank string." "el valor debe ser una cadena que no esté en blanco."}}} (mt/with-temporary-setting-values [site-locale "es"] diff --git a/test/metabase/api/dashboard_test.clj b/test/metabase/api/dashboard_test.clj index 8081a8521c4ac102249caea38d48066a85913598..ae4b312b4565c7d2ec0d3a2e25c2f57a5deb8f0c 100644 --- a/test/metabase/api/dashboard_test.clj +++ b/test/metabase/api/dashboard_test.clj @@ -123,7 +123,7 @@ creator (update :creator #(into {} %)) dashcards (update :dashcards #(mapv dashcard-response %))))) -(defn- do-with-dashboards-in-a-collection [grant-collection-perms-fn! dashboards-or-ids f] +(defn- do-with-dashboards-in-a-collection! [grant-collection-perms-fn! dashboards-or-ids f] (mt/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection collection] (grant-collection-perms-fn! (perms-group/all-users) collection) @@ -131,11 +131,11 @@ (t2/update! Dashboard (u/the-id dashboard-or-id) {:collection_id (u/the-id collection)})) (f)))) -(defmacro ^:private with-dashboards-in-readable-collection [dashboards-or-ids & body] - `(do-with-dashboards-in-a-collection perms/grant-collection-read-permissions! ~dashboards-or-ids (fn [] ~@body))) +(defmacro ^:private with-dashboards-in-readable-collection! [dashboards-or-ids & body] + `(do-with-dashboards-in-a-collection! perms/grant-collection-read-permissions! ~dashboards-or-ids (fn [] ~@body))) -(defmacro ^:private with-dashboards-in-writeable-collection [dashboards-or-ids & body] - `(do-with-dashboards-in-a-collection perms/grant-collection-readwrite-permissions! ~dashboards-or-ids (fn [] ~@body))) +(defmacro ^:private with-dashboards-in-writeable-collection! [dashboards-or-ids & body] + `(do-with-dashboards-in-a-collection! perms/grant-collection-readwrite-permissions! ~dashboards-or-ids (fn [] ~@body))) (defn do-with-simple-dashboard-with-tabs [f] @@ -463,8 +463,8 @@ :object (revision/serialize-instance dashboard dashboard-id dashboard)}] - (with-dashboards-in-readable-collection [dashboard-id] - (api.card-test/with-cards-in-readable-collection [card-id] + (with-dashboards-in-readable-collection! [dashboard-id] + (api.card-test/with-cards-in-readable-collection! [card-id] (is (=? {:name "Test Dashboard" :creator_id (mt/user->id :rasta) :collection true @@ -552,8 +552,8 @@ :parameter_mappings [{:card_id 1 :parameter_id "foo" :target [:dimension [:field field-id nil]]}]}] - (with-dashboards-in-readable-collection [dashboard-id] - (api.card-test/with-cards-in-readable-collection [card-id] + (with-dashboards-in-readable-collection! [dashboard-id] + (api.card-test/with-cards-in-readable-collection! [card-id] (is (=? {:name "Test Dashboard" :creator_id (mt/user->id :rasta) :collection_id true @@ -601,8 +601,8 @@ (mt/with-temp [Dashboard {dashboard-id :id} {:name "Test Dashboard"} Card {card-id :id} {:name "Dashboard Test Card"} DashboardCard _ {:dashboard_id dashboard-id, :card_id card-id}] - (with-dashboards-in-readable-collection [dashboard-id] - (api.card-test/with-cards-in-readable-collection [card-id] + (with-dashboards-in-readable-collection! [dashboard-id] + (api.card-test/with-cards-in-readable-collection! [card-id] (is (nil? (-> (dashboard-response (mt/user-http-request :rasta :get 200 (format "dashboard/%d" dashboard-id))) :collection_authority_level))) @@ -724,7 +724,7 @@ (mt/test-helpers-set-global-values! (mt/with-temporary-setting-values [synchronous-batch-updates true] (t2.with-temp/with-temp [Dashboard {dashboard-id :id} {:name "Test Dashboard"}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (testing "GET before update" (is (=? {:name "Test Dashboard" :creator_id (mt/user->id :rasta) @@ -797,7 +797,7 @@ (testing "PUT /api/dashboard/:id" (testing "allow `:caveats` and `:points_of_interest` to be empty strings, and `:show_in_getting_started` should be a boolean" (t2.with-temp/with-temp [Dashboard {dashboard-id :id} {:name "Test Dashboard"}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (is (=? {:name "Test Dashboard" :creator_id (mt/user->id :rasta) :collection true @@ -821,7 +821,7 @@ (testing "PUT /api/dashboard/:id" (testing "Can we clear the description of a Dashboard? (#4738)" (t2.with-temp/with-temp [Dashboard dashboard {:description "What a nice Dashboard"}] - (with-dashboards-in-writeable-collection [dashboard] + (with-dashboards-in-writeable-collection! [dashboard] (mt/user-http-request :rasta :put 200 (str "dashboard/" (u/the-id dashboard)) {:description nil}) (is (= nil (t2/select-one-fn :description Dashboard :id (u/the-id dashboard)))) @@ -834,7 +834,7 @@ (deftest update-dashboard-change-collection-id-test (testing "PUT /api/dashboard/:id" (testing "Can we change the Collection a Dashboard is in (assuming we have the permissions to do so)?" - (dashboard-test/with-dash-in-collection [_db collection dash] + (dashboard-test/with-dash-in-collection! [_db collection dash] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for both new and old collections (doseq [coll [collection new-collection]] @@ -847,7 +847,7 @@ (testing "if we don't have the Permissions for the old collection, we should get an Exception" (mt/with-non-admin-groups-no-root-collection-perms - (dashboard-test/with-dash-in-collection [_db _collection dash] + (dashboard-test/with-dash-in-collection! [_db _collection dash] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for only the *new* collection (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection) @@ -858,7 +858,7 @@ (testing "if we don't have the Permissions for the new collection, we should get an Exception" (mt/with-non-admin-groups-no-root-collection-perms - (dashboard-test/with-dash-in-collection [_db collection dash] + (dashboard-test/with-dash-in-collection! [_db collection dash] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for only the *old* collection (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) @@ -871,7 +871,7 @@ (testing "PUT /api/dashboard/:id" (testing "We can change the dashboard's width between 'fixed' and 'full' settings." (t2.with-temp/with-temp [Dashboard dashboard {}] - (with-dashboards-in-writeable-collection [dashboard] + (with-dashboards-in-writeable-collection! [dashboard] (testing "the default dashboard width value is 'fixed'." (is (= "fixed" (t2/select-one-fn :width Dashboard :id (u/the-id dashboard))))) @@ -893,7 +893,7 @@ (testing "PUT /api/dashboard/:id" (testing "We can add a time granularity parameter to a dashboard" (t2.with-temp/with-temp [Dashboard dashboard {}] - (with-dashboards-in-writeable-collection [dashboard] + (with-dashboards-in-writeable-collection! [dashboard] (testing "the dashboard starts with no parameters." (is (= [] (t2/select-one-fn :parameters Dashboard :id (u/the-id dashboard))))) @@ -1123,7 +1123,7 @@ (deftest delete-test (t2.with-temp/with-temp [Dashboard {dashboard-id :id}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (is (= nil (mt/user-http-request :rasta :delete 204 (format "dashboard/%d" dashboard-id)))) (is (= nil @@ -1606,7 +1606,7 @@ (deftest copy-dashboard-into-correct-collection-test (testing "POST /api/dashboard/:id/copy" (testing "Ensure the correct collection is set when copying" - (dashboard-test/with-dash-in-collection [_db collection dash] + (dashboard-test/with-dash-in-collection! [_db collection dash] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for both new and old collections (doseq [coll [collection new-collection]] @@ -1871,7 +1871,7 @@ (deftest e2e-update-tabs-only-test (testing "PUT /api/dashboard/:id/cards with create/update/delete tabs in a single req" (with-simple-dashboard-with-tabs [{:keys [dashboard-id dashtab-id-1 dashtab-id-2]}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] ;; send a request that update and create and delete some cards at the same time (is (some? (t2/select-one :model/DashboardTab :id dashtab-id-2))) (let [tabs (:tabs (mt/user-http-request :rasta :put 200 (format "dashboard/%d" dashboard-id) @@ -2010,8 +2010,8 @@ (deftest simple-creation-with-no-additional-series-test (mt/with-temp [Dashboard {dashboard-id :id} {} Card {card-id :id}] {} - (with-dashboards-in-writeable-collection [dashboard-id] - (api.card-test/with-cards-in-readable-collection [card-id] + (with-dashboards-in-writeable-collection! [dashboard-id] + (api.card-test/with-cards-in-readable-collection! [card-id] (let [resp (:dashcards (mt/user-http-request :rasta :put 200 (format "dashboard/%d" dashboard-id) {:dashcards [{:id -1 :card_id card-id @@ -2089,8 +2089,8 @@ (mt/with-temp [Dashboard {dashboard-id :id} {} Card {card-id :id} {} Card {series-id-1 :id} {:name "Series Card"}] - (with-dashboards-in-writeable-collection [dashboard-id] - (api.card-test/with-cards-in-readable-collection [card-id series-id-1] + (with-dashboards-in-writeable-collection! [dashboard-id] + (api.card-test/with-cards-in-readable-collection! [card-id series-id-1] (let [dashboard-cards (:dashcards (mt/user-http-request :crowberto :put 200 (format "dashboard/%d" dashboard-id) {:dashcards [{:id -1 :card_id card-id @@ -2220,7 +2220,7 @@ DashboardCard {dashcard-id-1 :id} {:dashboard_id dashboard-id, :card_id card-id} DashboardCard {dashcard-id-2 :id} {:dashboard_id dashboard-id, :card_id card-id} Card {series-id-1 :id} {:name "Series Card"}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (is (= {:size_x 4 :size_y 4 :col 0 @@ -2315,7 +2315,7 @@ :action_id action-id :card_id model-id} DashboardCard question-card {:dashboard_id dashboard-id, :card_id model-id}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] ;; TODO adds test for return ;; Update **both** cards to use the new card id (mt/user-http-request :rasta :put 200 (format "dashboard/%d" dashboard-id) @@ -2357,7 +2357,7 @@ DashboardCardSeries _ {:dashboardcard_id dashcard-id-1, :card_id series-id-1, :position 0} DashboardCardSeries _ {:dashboardcard_id dashcard-id-1, :card_id series-id-2, :position 1} DashboardCardSeries _ {:dashboardcard_id dashcard-id-3, :card_id series-id-1, :position 0}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (is (= 3 (count (t2/select-pks-set DashboardCard, :dashboard_id dashboard-id)))) (is (=? {:dashcards [{:id dashcard-id-3 @@ -2374,7 +2374,7 @@ Card {card-id :id} {} DashboardCard _ {:dashboard_id dashboard-id, :card_id card-id} DashboardCard _ {:dashboard_id dashboard-id, :card_id card-id}] - (with-dashboards-in-writeable-collection [dashboard-id] + (with-dashboards-in-writeable-collection! [dashboard-id] (is (= 2 (count (t2/select-pks-set DashboardCard, :dashboard_id dashboard-id)))) (is (=? {:tabs [] diff --git a/test/metabase/api/database_test.clj b/test/metabase/api/database_test.clj index 2f1e3c87a5a916475dce009c3ad087c8f7954d15..7030648fbe2c0eb19254c8af483872bb64189e9e 100644 --- a/test/metabase/api/database_test.clj +++ b/test/metabase/api/database_test.clj @@ -307,9 +307,9 @@ driver/can-connect? (constantly true)] ~@body))) -(defmacro with-db-scheduler-setup +(defmacro with-db-scheduler-setup! [& body] - `(mt/with-temp-scheduler + `(mt/with-temp-scheduler! (#'task.sync-databases/job-init) (u/prog1 ~@body (qs/delete-job (#'task/scheduler) (.getKey ^JobDetail @#'task.sync-databases/sync-analyze-job)) @@ -318,7 +318,7 @@ (deftest create-db-default-schedule-test (testing "POST /api/database" (testing "create a db with default scan options" - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (let [resp (mt/user-http-request :crowberto :post 200 "database" {:name (mt/random-name) @@ -1146,7 +1146,7 @@ (deftest create-db-with-manual-schedules-test (testing "POST /api/database" (testing "create a db with scan field values option is \"regularly on a schedule\"" - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (let [{:keys [details] :as db} (mt/user-http-request :crowberto :post 200 "database" @@ -1168,7 +1168,7 @@ (deftest create-db-never-scan-field-values-test (testing "POST /api/database" (testing "create a db with scan field values option is \"Never, I'll do it myself\"" - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (let [resp (mt/user-http-request :crowberto :post 200 "database" {:name (mt/random-name) @@ -1189,7 +1189,7 @@ (deftest create-db-on-demand-scan-field-values-test (testing "POST /api/database" (testing "create a db with scan field values option is \"Only when adding a new filter widget\"" - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (let [resp (mt/user-http-request :crowberto :post 200 "database" {:name (mt/random-name) @@ -1208,7 +1208,7 @@ (task.sync-databases-test/query-all-db-sync-triggers-name db))))))))) (deftest update-db-to-sync-on-custom-schedule-test - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (mt/with-temp [:model/Database db {}] @@ -1270,7 +1270,7 @@ (:cache_field_values_schedule db))))))))) (deftest update-db-to-never-scan-values-on-demand-test - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (mt/with-temp [:model/Database db {}] @@ -1292,7 +1292,7 @@ (is (nil? (:cache_field_values_schedule db))))))))) (deftest update-db-to-scan-field-values-on-demand-test - (with-db-scheduler-setup + (with-db-scheduler-setup! (with-test-driver-available! (testing "update db to scan on demand should remove scan field values trigger" (mt/with-temp @@ -1434,7 +1434,7 @@ (with-redefs [h2/*allow-testing-h2-connections* true] (mt/user-http-request user :post expected-status-code "database/validate" request-body)))) -(defn- test-connection-details [engine details] +(defn- test-connection-details! [engine details] (with-redefs [h2/*allow-testing-h2-connections* true] (#'api.database/test-connection-details engine details))) @@ -1447,7 +1447,7 @@ (testing "Underlying `test-connection-details` function should work" (is (= (:details (mt/db)) - (test-connection-details "h2" (:details (mt/db)))))) + (test-connection-details! "h2" (:details (mt/db)))))) (testing "Valid database connection details" (is (= (merge (:details (mt/db)) {:valid true}) @@ -1458,7 +1458,7 @@ (is (= {:errors {:db "check your connection string"} :message "Implicitly relative file paths are not allowed." :valid false} - (test-connection-details "h2" {:db "ABC"})))) + (test-connection-details! "h2" {:db "ABC"})))) (testing "via the API endpoint" (is (= {:errors {:db "check your connection string"} diff --git a/test/metabase/api/embed_test.clj b/test/metabase/api/embed_test.clj index d0ff6e112eb6b6fce57dceaa84d197496226046a..49e5aaa57296d524ec4ca18d27bc744577853cdb 100644 --- a/test/metabase/api/embed_test.clj +++ b/test/metabase/api/embed_test.clj @@ -39,13 +39,13 @@ (defn sign [claims] (jwt/sign claims *secret-key*)) -(defn do-with-new-secret-key [f] +(defn do-with-new-secret-key! [f] (binding [*secret-key* (random-embedding-secret-key)] (mt/with-temporary-setting-values [embedding-secret-key *secret-key*] (f)))) -(defmacro with-new-secret-key {:style/indent 0} [& body] - `(do-with-new-secret-key (fn [] ~@body))) +(defmacro with-new-secret-key! {:style/indent 0} [& body] + `(do-with-new-secret-key! (fn [] ~@body))) (defn card-token {:style/indent 1} [card-or-id & [additional-token-params]] (sign (merge {:resource {:question (u/the-id card-or-id)} @@ -98,9 +98,9 @@ (fn [~dashcard-binding] ~@body))) -(defmacro with-embedding-enabled-and-new-secret-key {:style/indent 0} [& body] +(defmacro with-embedding-enabled-and-new-secret-key! {:style/indent 0} [& body] `(mt/with-temporary-setting-values [~'enable-embedding true] - (with-new-secret-key + (with-new-secret-key! ~@body))) (defn ^:deprecated test-query-results @@ -161,20 +161,20 @@ (defn card-url [card & [additional-token-params]] (str "embed/card/" (card-token card additional-token-params))) (deftest it-should-be-possible-to-use-this-endpoint-successfully-if-all-the-conditions-are-met - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true}] (is (= successful-card-info (dissoc-id-and-name (client/client :get 200 (card-url card)))))))) (deftest we-should-fail-when-attempting-to-use-an-expired-token - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true}] (is (re= #"Token is expired.*" (client/client :get 400 (card-url card {:exp (buddy-util/to-timestamp yesterday)}))))))) (deftest bad-card-id-fails - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (let [card-url (str "embed/card/" (sign {:resource {:question "8"} :params {}}))] (is (= "Card id should be a positive integer." @@ -182,13 +182,13 @@ (deftest check-that-the-endpoint-doesn-t-work-if-embedding-isn-t-enabled (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (with-temp-card [card] (is (= "Embedding is not enabled." (client/client :get 400 (card-url card)))))))) (deftest check-that-if-embedding-is-enabled-globally-but-not-for-the-card-the-request-fails - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card] (is (= "Embedding is not enabled for this object." (client/client :get 400 (card-url card))))))) @@ -196,14 +196,14 @@ (deftest global-embedding-requests-fail-with-wrong-key (testing (str "check that if embedding is enabled globally and for the object that requests fail if they are signed " "with the wrong key") - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true}] (is (= "Message seems corrupt or manipulated" - (client/client :get 400 (with-new-secret-key (card-url card))))))))) + (client/client :get 400 (with-new-secret-key! (card-url card))))))))) (deftest check-that-only-enabled-params-that-are-not-present-in-the-jwt-come-back (testing "check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true :dataset_query {:database (mt/id) :type :native @@ -234,7 +234,7 @@ ;; because doing such migration is costly. ;; so there are cards where some parameters in template-tags does not exist in card.parameters ;; that why we need to keep concat both of them then dedupe by id - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card (public-test/card-with-embedded-params)] (is (= [;; the parameter with id = "c" exists in both card.parameters and tempalte-tags should have info ;; merge of both places @@ -262,7 +262,7 @@ (deftest parameters-should-include-relevant-template-tags-only (testing "should work with non-parameter template tags" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card (public-test/card-with-snippet-and-card-template-tags)] (is (= [{:type "date/single", :name "a", @@ -308,7 +308,7 @@ (do-response-formats [response-format _request-options] (testing "check that the endpoint doesn't work if embedding isn't enabled" (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (with-temp-card [card] (is (= "Embedding is not enabled." (client/real-client :get 400 (card-query-url card response-format)))))))))))) @@ -317,7 +317,7 @@ (testing "GET /api/embed/card/:token/query and GET /api/embed/card/:token/query/:export-format" (mt/test-helpers-set-global-values! (do-response-formats [response-format request-options] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (let [expected-status (response-format->status-code response-format)] (testing "it should be possible to run a Card successfully if you jump through the right hoops..." (with-temp-card [card {:enable_embedding true}] @@ -331,7 +331,7 @@ (testing "GET /api/embed/card/:token/query and GET /api/embed/card/:token/query/:export-format" (mt/test-helpers-set-global-values! (do-response-formats [response-format _request-options] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (let [expected-status (response-format->status-code response-format)] (testing (str "If the card has an invalid query we should just get a generic \"query failed\" " "exception (rather than leaking query info)") @@ -347,7 +347,7 @@ (testing "GET /api/embed/card/:token/query and GET /api/embed/card/:token/query/:export-format" (mt/test-helpers-set-global-values! (do-response-formats [response-format _request-options] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (testing "check that if embedding *is* enabled globally but not for the Card the request fails" (with-temp-card [card] (is (= "Embedding is not enabled for this object." @@ -357,19 +357,19 @@ (testing "GET /api/embed/card/:token/query and GET /api/embed/card/:token/query/:export-format" (mt/test-helpers-set-global-values! (do-response-formats [response-format _request-options] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (testing (str "check that if embedding is enabled globally and for the object that requests fail if they are " "signed with the wrong key") (with-temp-card [card {:enable_embedding true}] (is (= "Message seems corrupt or manipulated" - (client/real-client :get 400 (with-new-secret-key (card-query-url card response-format)))))))))))) + (client/real-client :get 400 (with-new-secret-key! (card-query-url card response-format)))))))))))) (deftest download-formatted-without-constraints-test (testing (str "Downloading CSV/JSON/XLSX results shouldn't be subject to the default query constraints -- even if " "the query comes in with `add-default-userland-constraints` (as will be the case if the query gets " "saved from one that had it -- see #9831 and #10399)") (with-redefs [qp.constraints/default-query-constraints (constantly {:max-results 10, :max-results-bare-rows 10})] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true :dataset_query (assoc (mt/mbql-query venues) :middleware @@ -381,7 +381,7 @@ (deftest card-locked-params-test (mt/test-helpers-set-global-values! - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true, :embedding_params {:venue_id "locked"}}] (do-response-formats [response-format request-options] (testing (str "check that if embedding is enabled globally and for the object requests fail if the token is " @@ -402,7 +402,7 @@ (client/client :get 400 (str (card-query-url card response-format {:params {:venue_id 100}}) "?venue_id=100")))))))))) (deftest card-disabled-params-test - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true, :embedding_params {:venue_id "disabled"}}] (do-response-formats [response-format _request-options] (testing (str "check that if embedding is enabled globally and for the object requests fail if they pass a " @@ -416,7 +416,7 @@ (deftest card-enabled-params-test (mt/test-helpers-set-global-values! - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-card [card {:enable_embedding true, :embedding_params {:venue_id "enabled"}}] (do-response-formats [response-format request-options] (testing "If `:enabled` param is present in both JWT and the URL, the request should fail" @@ -455,7 +455,7 @@ (deftest default-value-card-query-test (testing "GET /api/embed/card/:token/query with default values for params" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (testing "if the param is enabled" (t2.with-temp/with-temp [Card card (assoc (card-with-date-field-filter-default) :embedding_params {:date :enabled})] @@ -507,14 +507,14 @@ (deftest csv-reports-count (testing "make sure CSV (etc.) downloads take editable params into account (#6407)" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card (card-with-date-field-filter)] (is (= "count\n107\n" (client/client :get 200 (str (card-query-url card "/csv") "?date=Q1-2014")))))))) (deftest csv-forward-url-test (mt/test-helpers-set-global-values! - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (mt/with-temp [Card card (card-with-date-field-filter)] ;; make sure the URL doesn't include /api/ at the beginning like it normally would (binding [client/*url-prefix* ""] @@ -529,34 +529,34 @@ (str "embed/dashboard/" (dash-token dashboard additional-token-params))) (deftest it-should-be-possible-to-call-this-endpoint-successfully - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash {:enable_embedding true}] (is (= successful-dashboard-info (dissoc-id-and-name (client/client :get 200 (dashboard-url dash)))))))) (deftest bad-dashboard-id-fails - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (let [dashboard-url (str "embed/dashboard/" (sign {:resource {:dashboard "8"} :params {}}))] (is (= "Dashboard id should be a positive integer." (client/client :get 400 dashboard-url)))))) (deftest we-should-fail-when-attempting-to-use-an-expired-token-2 - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash {:enable_embedding true}] (is (re= #"^Token is expired.*" (client/client :get 400 (dashboard-url dash {:exp (buddy-util/to-timestamp yesterday)}))))))) (deftest check-that-the-dashboard-endpoint-doesn-t-work-if-embedding-isn-t-enabled (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (t2.with-temp/with-temp [Dashboard dash] (is (= "Embedding is not enabled." (client/client :get 400 (dashboard-url dash)))))))) (deftest check-that-if-embedding--is--enabled-globally-but-not-for-the-dashboard-the-request-fails - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash] (is (= "Embedding is not enabled for this object." (client/client :get 400 (dashboard-url dash))))))) @@ -564,14 +564,14 @@ (deftest global-embedding-check-key (testing (str "check that if embedding is enabled globally and for the object that requests fail if they are signed " "with the wrong key") - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash {:enable_embedding true}] (is (= "Message seems corrupt or manipulated" - (client/client :get 400 (with-new-secret-key (dashboard-url dash))))))))) + (client/client :get 400 (with-new-secret-key! (dashboard-url dash))))))))) (deftest only-enabled-params-that-are-not-present-in-the-jwt-come-back (testing "check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash {:enable_embedding true :embedding_params {:a "locked", :b "disabled", :c "enabled", :d "enabled"} :parameters [{:id "_a", :slug "a", :name "a", :type "date"} @@ -583,7 +583,7 @@ (deftest locked-params-are-substituted-into-text-cards (testing "check that locked params are substituted into text cards with mapped variables on the backend" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (mt/with-temp [Dashboard dash {:enable_embedding true :parameters [{:id "_a" :slug "a" :name "a" :type :string/=}]} DashboardCard _ {:dashboard_id (:id dash) @@ -600,7 +600,7 @@ (deftest locked-params-removes-values-fields-and-mappings-test (testing "check that locked params are removed in parameter mappings, param_values, and param_fields" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dashboard {:enable_embedding true :embedding_params {:venue_name "locked"} :name "Test Dashboard" @@ -639,7 +639,7 @@ (deftest locked-params-removes-values-fields-when-not-used-in-enabled-params (testing "check that locked params are not removed in parameter mappings, param_values, and param_fields when an enabled param uses them (#37914)" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dashboard {:enable_embedding true :embedding_params {:venue_name "locked" :venue_name_2 "enabled"} @@ -685,7 +685,7 @@ (deftest linked-param-to-locked-removes-param-values-test (testing "Check that a linked parameter to a locked params we remove the param_values." - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dashboard {:enable_embedding true :embedding_params {:venue_name "locked" :category_name "enabled"} :name "Test Dashboard" @@ -731,7 +731,7 @@ (deftest it-should-be-possible-to-run-a-card-successfully-if-you-jump-through-the-right-hoops--- (testing "it should be possible to run a Card successfully if you jump through the right hoops..." - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true}}] #_{:clj-kondo/ignore [:deprecated-var]} (test-query-results (client/client :get 202 (dashcard-url dashcard))))))) @@ -740,7 +740,7 @@ (testing (str "Downloading CSV/JSON/XLSX results from the dashcard endpoint shouldn't be subject to the default " "query constraints (#10399)") (with-redefs [qp.constraints/default-query-constraints (constantly {:max-results 10, :max-results-bare-rows 10})] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true} :card {:dataset_query (assoc (mt/mbql-query venues) :middleware @@ -755,7 +755,7 @@ ;; Clear out the query execution log so that test doesn't read stale state (t2/delete! :model/QueryExecution) (mt/test-helpers-set-global-values! - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true} :card {:dataset_query (mt/mbql-query venues)}}] (let [query (assoc @@ -768,17 +768,17 @@ :viz-settings {} :async? true :cache-strategy nil)] - (process-userland-query-test/with-query-execution [qe query] + (process-userland-query-test/with-query-execution! [qe query] (client/client :get 200 (str (dashcard-url dashcard) "/csv")) (is (= :embedded-csv-download (:context (qe))))) - (process-userland-query-test/with-query-execution [qe query] + (process-userland-query-test/with-query-execution! [qe query] (client/client :get 200 (str (dashcard-url dashcard) "/json")) (is (= :embedded-json-download (:context (qe))))) - (process-userland-query-test/with-query-execution [qe query] + (process-userland-query-test/with-query-execution! [qe query] (client/client :get 200 (str (dashcard-url dashcard) "/xlsx")) (is (= :embedded-xlsx-download (:context @@ -787,7 +787,7 @@ (deftest downloading-csv-json-xlsx-results-from-the-dashcard-endpoint-respects-column-settings (testing "Downloading CSV/JSON/XLSX results should respect the column settings of the dashcard, such as column order and hidden/shown setting. (#33727)" (with-redefs [qp.constraints/default-query-constraints (constantly {:max-results 10, :max-results-bare-rows 10})] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true} :card {:dataset_query (assoc (mt/mbql-query venues) :limit 1 @@ -811,7 +811,7 @@ (deftest generic-query-failed-exception-test (testing (str "...but if the card has an invalid query we should just get a generic \"query failed\" exception " "(rather than leaking query info)") - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true} :card {:dataset_query (mt/native-query {:query "SELECT * FROM XYZ"})}}] (is (= {:status "failed" @@ -821,13 +821,13 @@ (deftest check-that-the-dashcard-endpoint-doesn-t-work-if-embedding-isn-t-enabled (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (with-temp-dashcard [dashcard] (is (= "Embedding is not enabled." (client/client :get 400 (dashcard-url dashcard)))))))) (deftest dashcard-check-that-if-embedding--is--enabled-globally-but-not-for-the-dashboard-the-request-fails - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard] (is (= "Embedding is not enabled for this object." (client/client :get 400 (dashcard-url dashcard))))))) @@ -835,13 +835,13 @@ (deftest dashcard-global-embedding-check-key (testing (str "check that if embedding is enabled globally and for the object that requests fail if they are signed " "with the wrong key") - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true}}] (is (= "Message seems corrupt or manipulated" - (client/client :get 400 (with-new-secret-key (dashcard-url dashcard))))))))) + (client/client :get 400 (with-new-secret-key! (dashcard-url dashcard))))))))) (deftest dashboard-locked-params-test - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true, :embedding_params {:venue_id "locked"}}}] (testing (str "check that if embedding is enabled globally and for the object requests fail if the token is " "missing a `:locked` parameter") @@ -858,7 +858,7 @@ (client/client :get 400 (str (dashcard-url dashcard) "?venue_id=100")))))))) (deftest dashboard-disabled-params-test - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true, :embedding_params {:venue_id "disabled"}}}] (testing (str "check that if embedding is enabled globally and for the object requests fail if they pass a " "`:disabled` parameter") @@ -870,7 +870,7 @@ (client/client :get 400 (str (dashcard-url dashcard) "?venue_id=200")))))))) (deftest dashboard-enabled-params-test - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true, :embedding_params {:venue_id "enabled"}}}] (testing "If `:enabled` param is present in both JWT and the URL, the request should fail" (is (= "You can't specify a value for :venue_id if it's already set in the JWT." @@ -888,7 +888,7 @@ (deftest dashboard-native-query-params-with-default-test (testing "GET api/embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id with default values for params" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card (card-with-date-field-filter-default) Dashboard dashboard {:enable_embedding true @@ -947,7 +947,7 @@ (deftest make-sure-that-multiline-series-word-as-expected---4768- (testing "make sure that multiline series word as expected (#4768)" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card series-card {:dataset_query {:database (mt/id) :type :query :query {:source-table (mt/id :venues)}}}] @@ -970,16 +970,16 @@ (u/the-id field-or-id) "/values")) -(defn- do-with-embedding-enabled-and-temp-card-referencing {:style/indent 2} [table-kw field-kw f] - (with-embedding-enabled-and-new-secret-key +(defn- do-with-embedding-enabled-and-temp-card-referencing! {:style/indent 2} [table-kw field-kw f] + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card (assoc (public-test/mbql-card-referencing table-kw field-kw) :enable_embedding true)] (f card)))) -(defmacro ^:private with-embedding-enabled-and-temp-card-referencing +(defmacro ^:private with-embedding-enabled-and-temp-card-referencing! {:style/indent 3} [table-kw field-kw [card-binding] & body] - `(do-with-embedding-enabled-and-temp-card-referencing ~table-kw ~field-kw + `(do-with-embedding-enabled-and-temp-card-referencing! ~table-kw ~field-kw (fn [~(or card-binding '_)] ~@body))) @@ -993,26 +993,26 @@ ["BCD Tofu House"]] :field_id (mt/id :venues :name) :has_more_values false} - (with-embedding-enabled-and-temp-card-referencing :venues :name [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :name [card] (-> (client/client :get 200 (field-values-url card (mt/id :venues :name))) (update :values (partial take 5))))))) ;; but for Fields that are not referenced we should get an Exception (deftest but-for-fields-that-are-not-referenced-we-should-get-an-exception (is (= "Not found." - (with-embedding-enabled-and-temp-card-referencing :venues :name [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :name [card] (client/client :get 400 (field-values-url card (mt/id :venues :price))))))) ;; Endpoint should fail if embedding is disabled (deftest endpoint-should-fail-if-embedding-is-disabled (is (= "Embedding is not enabled." - (with-embedding-enabled-and-temp-card-referencing :venues :name [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :name [card] (mt/with-temporary-setting-values [enable-embedding false] (client/client :get 400 (field-values-url card (mt/id :venues :name)))))))) (deftest embedding-not-enabled-message (is (= "Embedding is not enabled for this object." - (with-embedding-enabled-and-temp-card-referencing :venues :name [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :name [card] (t2/update! Card (u/the-id card) {:enable_embedding false}) (client/client :get 400 (field-values-url card (mt/id :venues :name))))))) @@ -1024,7 +1024,7 @@ (client/client :get 200 (format "embed/card/%s/params/%s/values" (card-token card) param-key)))] (mt/with-temporary-setting-values [enable-embedding true] - (with-new-secret-key + (with-new-secret-key! (api.card-test/with-card-param-values-fixtures [{:keys [card field-filter-card param-keys]}] (t2/update! Card (:id field-filter-card) {:enable_embedding true @@ -1065,8 +1065,8 @@ ;;; ----------------------------- GET /api/embed/dashboard/:token/field/:field/values nil ----------------------------- -(defn- do-with-embedding-enabled-and-temp-dashcard-referencing {:style/indent 2} [table-kw field-kw f] - (with-embedding-enabled-and-new-secret-key +(defn- do-with-embedding-enabled-and-temp-dashcard-referencing! {:style/indent 2} [table-kw field-kw f] + (with-embedding-enabled-and-new-secret-key! (mt/with-temp [Dashboard dashboard {:enable_embedding true} Card card (public-test/mbql-card-referencing table-kw field-kw) DashboardCard dashcard {:dashboard_id (u/the-id dashboard) @@ -1078,10 +1078,10 @@ (f dashboard card dashcard)))) -(defmacro ^:private with-embedding-enabled-and-temp-dashcard-referencing +(defmacro ^:private with-embedding-enabled-and-temp-dashcard-referencing! {:style/indent 3} [table-kw field-kw [dash-binding card-binding dashcard-binding] & body] - `(do-with-embedding-enabled-and-temp-dashcard-referencing ~table-kw ~field-kw + `(do-with-embedding-enabled-and-temp-dashcard-referencing! ~table-kw ~field-kw (fn [~(or dash-binding '_) ~(or card-binding '_) ~(or dashcard-binding '_)] ~@body))) @@ -1094,20 +1094,20 @@ ["BCD Tofu House"]] :field_id (mt/id :venues :name) :has_more_values false} - (with-embedding-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (-> (client/client :get 200 (field-values-url dashboard (mt/id :venues :name))) (update :values (partial take 5))))))) ;; shound NOT be able to use the endpoint with a Field not referenced by the Dashboard (deftest shound-not-be-able-to-use-the-endpoint-with-a-field-not-referenced-by-the-dashboard (is (= "Not found." - (with-embedding-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (client/client :get 400 (field-values-url dashboard (mt/id :venues :price))))))) ;; Endpoint should fail if embedding is disabled (deftest field-values-endpoint-should-fail-if-embedding-is-disabled (is (= "Embedding is not enabled." - (with-embedding-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (mt/with-temporary-setting-values [enable-embedding false] (client/client :get 400 (field-values-url dashboard (mt/id :venues :name)))))))) @@ -1115,7 +1115,7 @@ ;; Endpoint should fail if embedding is disabled for the Dashboard (deftest endpoint-should-fail-if-embedding-is-disabled-for-the-dashboard (is (= "Embedding is not enabled for this object." - (with-embedding-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (t2/update! Dashboard (u/the-id dashboard) {:enable_embedding false}) (client/client :get 400 (field-values-url dashboard (mt/id :venues :name))))))) @@ -1155,11 +1155,11 @@ :value "33 T")))))] (testing "GET /api/embed/card/:token/field/:field/search/:search-field-id nil" (testing "Search for Field values for a Card" - (with-embedding-enabled-and-temp-card-referencing :venues :id [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :id [card] (tests Card card)))) (testing "GET /api/embed/dashboard/:token/field/:field/search/:search-field-id nil" (testing "Search for Field values for a Dashboard" - (with-embedding-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (tests Dashboard dashboard))))))) @@ -1199,28 +1199,28 @@ (testing "GET /api/embed/card/:token/field/:field/remapping/:remapped-id nil" (testing "Get remapped Field values for a Card" - (with-embedding-enabled-and-temp-card-referencing :venues :id [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :id [card] (tests Card card))) (testing "Shouldn't work if Card doesn't reference the Field in question" - (with-embedding-enabled-and-temp-card-referencing :venues :price [card] + (with-embedding-enabled-and-temp-card-referencing! :venues :price [card] (is (= "Not found." (client/client :get 400 (field-remapping-url card (mt/id :venues :id) (mt/id :venues :name)) :value "10")))))) (testing "GET /api/embed/dashboard/:token/field/:field/remapping/:remapped-id nil" (testing "Get remapped Field values for a Dashboard" - (with-embedding-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (tests Dashboard dashboard))) (testing "Shouldn't work if Dashboard doesn't reference the Field in question" - (with-embedding-enabled-and-temp-dashcard-referencing :venues :price [dashboard] + (with-embedding-enabled-and-temp-dashcard-referencing! :venues :price [dashboard] (is (= "Not found." (client/client :get 400 (field-remapping-url dashboard (mt/id :venues :id) (mt/id :venues :name)) :value "10")))))))) ;;; ------------------------------------------------ Chain filtering ------------------------------------------------- -(defn- do-with-chain-filter-fixtures [f] - (with-embedding-enabled-and-new-secret-key +(defn- do-with-chain-filter-fixtures! [f] + (with-embedding-enabled-and-new-secret-key! (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard], :as m}] (t2/update! Dashboard (u/the-id dashboard) {:enable_embedding true}) (letfn [(token [params] @@ -1236,11 +1236,11 @@ :values-url values-url :search-url search-url)))))) -(defmacro ^:private with-chain-filter-fixtures [[binding] & body] - `(do-with-chain-filter-fixtures (fn [~binding] ~@body))) +(defmacro ^:private with-chain-filter-fixtures! [[binding] & body] + `(do-with-chain-filter-fixtures! (fn [~binding] ~@body))) (deftest chain-filter-embedding-disabled-test - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (testing "without embedding enabled for dashboard" (t2/update! Dashboard (u/the-id dashboard) {:enable_embedding false}) (testing "GET /api/embed/dashboard/:token/params/:param-key/values" @@ -1251,7 +1251,7 @@ (client/client :get 400 (search-url)))))))) (deftest chain-filter-random-params-test - (with-chain-filter-fixtures [{:keys [values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [values-url search-url]}] (testing "Requests should fail if parameter is not explicitly enabled" (testing "\nGET /api/embed/dashboard/:token/params/:param-key/values" (is (= "Cannot search for values: \"category_id\" is not an enabled parameter." @@ -1262,7 +1262,7 @@ (deftest params-with-static-list-test (testing "embedding with parameter that has source is a static list" - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (t2/update! Dashboard (:id dashboard) {:embedding_params {"static_category" "enabled", "static_category_label" "enabled"}}) (testing "Should work if the param we're fetching values for is enabled" @@ -1276,7 +1276,7 @@ (client/client :get 200 (search-url {} "_STATIC_CATEGORY_LABEL_" "AF"))))))))) (deftest chain-filter-enabled-params-test - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (t2/update! Dashboard (:id dashboard) {:embedding_params {"category_id" "enabled", "category_name" "enabled", "price" "enabled"}}) (testing "Should work if the param we're fetching values for is enabled" @@ -1320,7 +1320,7 @@ (testing "Should not fail if request is authenticated but current user does not have data permissions" (mt/with-temp-copy-of-db (mt/with-no-data-perms-for-all-users! - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (t2/update! Dashboard (:id dashboard) {:embedding_params {"category_id" "enabled", "category_name" "enabled", "price" "enabled"}}) (testing "Should work if the param we're fetching values for is enabled" @@ -1334,7 +1334,7 @@ (chain-filer-test/take-n-values 3 (mt/user-http-request :rasta :get 200 (search-url)))))))))))) (deftest chain-filter-locked-params-test - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (testing "Requests should fail if searched param is locked" (t2/update! Dashboard (:id dashboard) {:embedding_params {"category_id" "locked", "category_name" "locked"}}) @@ -1371,7 +1371,7 @@ (client/client :get 400 (str url "?_PRICE_=4")))))))))) (deftest chain-filter-disabled-params-test - (with-chain-filter-fixtures [{:keys [dashboard values-url search-url]}] + (with-chain-filter-fixtures! [{:keys [dashboard values-url search-url]}] (testing "Requests should fail if searched param is disabled" (t2/update! Dashboard (:id dashboard) {:embedding_params {"category_id" "disabled", "category_name" "disabled"}}) @@ -1412,12 +1412,12 @@ (testing "GET /api/embed/pivot/card/:token/query" (testing "check that the endpoint doesn't work if embedding isn't enabled" (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (with-temp-card [card (api.pivots/pivot-card)] (is (= "Embedding is not enabled." (client/client :get 400 (pivot-card-query-url card "")))))))) - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (let [expected-status 202] (testing "it should be possible to run a Card successfully if you jump through the right hoops..." (with-temp-card [card (merge {:enable_embedding true} (api.pivots/pivot-card))] @@ -1437,7 +1437,7 @@ "signed with the wrong key") (with-temp-card [card (merge {:enable_embedding true} (api.pivots/pivot-card))] (is (= "Message seems corrupt or manipulated" - (client/client :get 400 (with-new-secret-key (pivot-card-query-url card "")))))))))))) + (client/client :get 400 (with-new-secret-key! (pivot-card-query-url card "")))))))))))) (defn- pivot-dashcard-url [dashcard & [additional-token-params]] (str "embed/pivot/dashboard/" (dash-token (:dashboard_id dashcard) additional-token-params) @@ -1447,7 +1447,7 @@ (deftest pivot-dashcard-success-test (mt/test-drivers (api.pivots/applicable-drivers) (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true, :parameters []} :card (api.pivots/pivot-card) :dashcard {:parameter_mappings []}}] @@ -1461,7 +1461,7 @@ (deftest pivot-dashcard-embedding-disabled-test (mt/dataset test-data (mt/with-temporary-setting-values [enable-embedding false] - (with-new-secret-key + (with-new-secret-key! (with-temp-dashcard [dashcard {:dash {:parameters []} :card (api.pivots/pivot-card) :dashcard {:parameter_mappings []}}] @@ -1470,7 +1470,7 @@ (deftest pivot-dashcard-embedding-disabled-for-card-test (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:parameters []} :card (api.pivots/pivot-card) :dashcard {:parameter_mappings []}}] @@ -1481,17 +1481,17 @@ (mt/dataset test-data (testing (str "check that if embedding is enabled globally and for the object that requests fail if they are signed " "with the wrong key") - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true, :parameters []} :card (api.pivots/pivot-card) :dashcard {:parameter_mappings []}}] (is (= "Message seems corrupt or manipulated" - (client/client :get 400 (with-new-secret-key (pivot-dashcard-url dashcard)))))))))) + (client/client :get 400 (with-new-secret-key! (pivot-dashcard-url dashcard)))))))))) (deftest pivot-dashcard-locked-params-test (mt/dataset test-data (mt/test-helpers-set-global-values! - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true :embedding_params {:abc "locked"} :parameters [{:id "_ORDER_ID_" @@ -1520,7 +1520,7 @@ (deftest pivot-dashcard-disabled-params-test (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true :embedding_params {:abc "disabled"} :parameters []} @@ -1537,7 +1537,7 @@ (deftest pivot-dashcard-enabled-params-test (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true :embedding_params {:abc "enabled"} :parameters [{:id "_ORDER_ID_" @@ -1585,7 +1585,7 @@ (deftest handle-single-params-for-operator-filters-test (testing "Query endpoints should work with a single URL parameter for an operator filter (#20438)" (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card {card-id :id, :as card} {:dataset_query (mt/native-query {:query "SELECT count(*) AS count FROM PUBLIC.PEOPLE WHERE true [[AND {{NAME}}]]" :template-tags {"NAME" @@ -1623,7 +1623,7 @@ (deftest pass-numeric-param-as-number-test (testing "Embedded numeric params should work with numeric (as opposed to string) values in the JWT (#20845)" (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card {:dataset_query (mt/native-query {:query "SELECT count(*) FROM orders WHERE quantity = {{qty_locked}}" :template-tags {"qty_locked" {:name "qty_locked" @@ -1641,7 +1641,7 @@ :native {:query "SELECT 2000 AS number, '2024-03-26'::DATE AS date;"}} output-helper {:csv (fn [output] (->> output csv/read-csv last)) :json (fn [output] (->> output (map (juxt :NUMBER :DATE)) last))}] - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card {card-id :id} {:enable_embedding true :display :table :dataset_query q} Dashboard {dashboard-id :id} {:enable_embedding true @@ -1669,7 +1669,7 @@ (deftest filter-linked-to-locked-filter-test (testing "Filter linked to locked filter works in various common configurations." (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card {card-id :id} {:enable_embedding true :display :table :dataset_query {:database (mt/id) @@ -1739,7 +1739,7 @@ (deftest querying-a-dashboard-dashcard-updates-last-viewed-at (mt/test-helpers-set-global-values! (mt/dataset test-data - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (with-temp-dashcard [dashcard {:dash {:enable_embedding true :last_viewed_at #t "2000-01-01"}}] (let [dashboard-id (t2/select-one-fn :id :model/Dashboard :id (:dashboard_id dashcard)) diff --git a/test/metabase/api/ldap_test.clj b/test/metabase/api/ldap_test.clj index 99e17ab6ac3c151a58d823d48a20050d84457e12..cd47e76df482f40ac21752e2a506cd8029042d78 100644 --- a/test/metabase/api/ldap_test.clj +++ b/test/metabase/api/ldap_test.clj @@ -17,7 +17,7 @@ (deftest ldap-settings-test (testing "PUT /api/ldap/settings" - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "Valid LDAP settings can be saved via an API call" (mt/user-http-request :crowberto :put 200 "ldap/settings" (ldap-test-details))) @@ -54,7 +54,7 @@ (assoc (ldap-test-details) :ldap-port "" :ldap-enabled false)))))))) (deftest ldap-enabled-test - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "`ldap-enabled` setting validates currently saved LDAP settings" (mt/with-temporary-setting-values [ldap-enabled false] (with-redefs [ldap/test-current-ldap-details (constantly {:status :ERROR :message "test error"})] diff --git a/test/metabase/api/persist_test.clj b/test/metabase/api/persist_test.clj index 775ac2784b86cdfaa0319835d6371670ad504962..c4bc5a44792f6e33a94d844a339b27ad76221c37 100644 --- a/test/metabase/api/persist_test.clj +++ b/test/metabase/api/persist_test.clj @@ -11,22 +11,22 @@ (def ^:private default-cron "0 0 0/12 * * ? *") -(defn- do-with-setup [f] - (mt/with-temp-scheduler +(defn- do-with-setup! [f] + (mt/with-temp-scheduler! (#'task.persist-refresh/job-init!) (mt/with-temporary-setting-values [:persisted-models-enabled true] (mt/with-temp [Database db {:settings {:persist-models-enabled true}}] (task.persist-refresh/schedule-persistence-for-database! db default-cron) (f db))))) -(defmacro ^:private with-setup +(defmacro ^:private with-setup! "Sets up a temp scheduler, a temp database and enabled persistence" [db-binding & body] - `(do-with-setup (fn [~db-binding] ~@body))) + `(do-with-setup! (fn [~db-binding] ~@body))) (deftest set-refresh-schedule-test (testing "Setting new cron schedule reschedules refresh tasks" - (with-setup db + (with-setup! db (is (= default-cron (get-in (task.persist-refresh/job-info-by-db-id) [(:id db) :schedule]))) (let [new-schedule "0 0 0/12 * * ? *"] @@ -36,7 +36,7 @@ (get-in (task.persist-refresh/job-info-by-db-id) [(:id db) :schedule])))))) (testing "Prevents setting a year value" - (with-setup db + (with-setup! db (let [bad-schedule "0 0 0/12 * * ? 1995"] (is (= "Must be a valid cron string not specifying a year" (mt/user-http-request :crowberto :post 400 "persist/set-refresh-schedule" @@ -46,7 +46,7 @@ [(:id db) :schedule]))))))) (deftest persisted-info-by-id-test - (with-setup db + (with-setup! db (mt/with-temp [:model/Card model {:database_id (u/the-id db), :type :model} :model/PersistedInfo pinfo {:database_id (u/the-id db), :card_id (u/the-id model)}] @@ -64,7 +64,7 @@ (mt/user-http-request :crowberto :get 200 (format "persist/%d" (u/the-id pinfo))))))))) (deftest persisted-info-by-card-id-test - (with-setup db + (with-setup! db (mt/with-temp [:model/Card model {:database_id (u/the-id db), :type :model} :model/PersistedInfo pinfo {:database_id (u/the-id db), :card_id (u/the-id model)}] diff --git a/test/metabase/api/preview_embed_test.clj b/test/metabase/api/preview_embed_test.clj index 9e17391a860140d7fa5cf1fbc4cbd4fc6b1e95bc..79a74041c12c6c3d03422cc0436abdbd0bbf533e 100644 --- a/test/metabase/api/preview_embed_test.clj +++ b/test/metabase/api/preview_embed_test.clj @@ -23,7 +23,7 @@ (deftest card-test (testing "GET /api/preview_embed/card/:token" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card] (testing "it should be possible to use this endpoint successfully if all the conditions are met" (is (= embed-test/successful-card-info @@ -42,7 +42,7 @@ (testing "check that if embedding is enabled globally requests fail if they are signed with the wrong key" (is (= "Message seems corrupt or manipulated" - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (card-url card)))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (card-url card)))))) (testing "Check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back" (embed-test/with-temp-card [card {:dataset_query @@ -75,7 +75,7 @@ (deftest query-test (testing "GET /api/preview_embed/card/:token/query" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card] (testing "It should be possible to run a Card successfully if you jump through the right hoops..." #_{:clj-kondo/ignore [:deprecated-var]} @@ -93,12 +93,12 @@ (testing "check that if embedding is enabled globally requests fail if they are signed with the wrong key" (is (= "Message seems corrupt or manipulated" - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (card-query-url card)))))))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (card-query-url card)))))))))) (deftest query-locked-params-test (testing "GET /api/preview_embed/card/:token/query" (testing "LOCKED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card] (testing "check that if embedding is enabled globally fail if the token is missing a `:locked` parameter" (is (= "You must specify a value for :venue_id in the JWT." @@ -119,7 +119,7 @@ (deftest query-disabled-params-test (testing "GET /api/preview_embed/card/:token/query" (testing "DISABLED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card] (testing "check that if embedding is enabled globally and for the object requests fail if they pass a `:disabled` parameter" (is (= "You're not allowed to specify a value for :venue_id." @@ -134,7 +134,7 @@ (deftest query-enabled-params-test (testing "GET /api/preview_embed/card/:token/query" (testing "ENABLED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card] (testing "If `:enabled` param is present in both JWT and the URL, the request should fail" (is (= "You can't specify a value for :venue_id if it's already set in the JWT." @@ -154,7 +154,7 @@ "?venue_id=200"))))))))) (deftest default-value-card-query-test (testing "GET /api/preview_embed/card/:token/query with default values for params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (testing "if the param is enabled" (t2.with-temp/with-temp [Card card (assoc (embed-test/card-with-date-field-filter-default) :embedding_params {:date "enabled"})] @@ -209,7 +209,7 @@ expected-row-count 1] (with-redefs [api.preview-embed/max-results expected-row-count] (mt/dataset test-data - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (let [sample-db-orders-question (mt/query orders)] (embed-test/with-temp-card [card {:dataset_query sample-db-orders-question}] (let [limited (count @@ -226,7 +226,7 @@ (deftest dashboard-test (testing "GET /api/preview_embed/dashboard/:token" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash] (testing "it should be possible to call this endpoint successfully..." (is (= embed-test/successful-dashboard-info @@ -244,11 +244,11 @@ (testing "check that if embedding is enabled globally requests fail if they are signed with the wrong key" (is (= "Message seems corrupt or manipulated" - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (dashboard-url dash)))))))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (dashboard-url dash)))))))))) (deftest only-enabled-params-not-in-jwt-test (testing "Check that only ENABLED params that ARE NOT PRESENT IN THE JWT come back" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Dashboard dash {:parameters [{:id "_a", :slug "a", :name "a", :type "date"} {:id "_b", :slug "b", :name "b", :type "date"} {:id "_c", :slug "c", :name "c", :type "date"} @@ -271,7 +271,7 @@ (deftest dashcard-test (testing "/api/preview_embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard] (testing "It should be possible to run a Card successfully if you jump through the right hoops..." #_{:clj-kondo/ignore [:deprecated-var]} @@ -289,12 +289,12 @@ (testing "check that if embedding is enabled globally requests fail if they are signed with the wrong key" (is (= "Message seems corrupt or manipulated" - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (dashcard-url dashcard)))))))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (dashcard-url dashcard)))))))))) (deftest dashcard-locked-params-test (testing "/api/preview_embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id" (testing "LOCKED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard] (testing "check that if embedding is enabled globally fail if the token is missing a `:locked` parameter" (is (= "You must specify a value for :venue_id in the JWT." @@ -316,7 +316,7 @@ (deftest dashcard-disabled-params-test (testing "/api/preview_embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id" (testing "DISABLED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard] (testing "check that if embedding is enabled globally and for the object requests fail if they pass a `:disabled` parameter" (is (= "You're not allowed to specify a value for :venue_id." @@ -331,7 +331,7 @@ (deftest dashcard-disabled-params-test-2 (testing "/api/preview_embed/dashboard/:token/dashcard/:dashcard-id/card/:card-id" (testing "ENABLED params" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard] (testing "If `:enabled` param is present in both JWT and the URL, the request should fail" (is (= "You can't specify a value for :venue_id if it's already set in the JWT." @@ -355,7 +355,7 @@ (testing (str "Check that editable query params work correctly and keys get coverted from strings to keywords, even " "if they're something that our middleware doesn't normally assume is implicitly convertable to a " "keyword. See `ring.middleware.keyword-params/keyword-syntax?` (#6783)") - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard {:dash {:enable_embedding true :parameters [{:id "_SECOND_DATE_SEEN_" :slug "2nd_date_seen" @@ -374,7 +374,7 @@ (deftest editable-params-should-not-be-invalid-test (testing "Make sure that editable params do not result in \"Invalid Parameter\" exceptions (#7212)" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card {:dataset_query {:database (mt/id) :type :native :native {:query "SELECT {{num}} AS num" @@ -400,7 +400,7 @@ (deftest postgres-convert-parameters-to-numbers-test (mt/test-driver :postgres (testing "Make sure that ID params correctly get converted to numbers as needed (Postgres-specific)..." - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card card {:dataset_query {:database (mt/id) :type :query :query {:source-table (mt/id :venues) @@ -432,7 +432,7 @@ (mt/dataset test-data (testing "GET /api/preview_embed/pivot/card/:token/query" (testing "successful preview" - (let [result (embed-test/with-embedding-enabled-and-new-secret-key + (let [result (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card (api.pivots/pivot-card)] (mt/user-http-request :crowberto :get 202 (pivot-card-query-url card)))) rows (mt/rows result)] @@ -443,22 +443,22 @@ (testing "should fail if user is not an admin" (is (= "You don't have permissions to do that." - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card (api.pivots/pivot-card)] (mt/user-http-request :rasta :get 403 (pivot-card-query-url card))))))) (testing "should fail if embedding is disabled" (is (= "Embedding is not enabled." (mt/with-temporary-setting-values [enable-embedding false] - (embed-test/with-new-secret-key + (embed-test/with-new-secret-key! (embed-test/with-temp-card [card (api.pivots/pivot-card)] (mt/user-http-request :crowberto :get 400 (pivot-card-query-url card)))))))) (testing "should fail if embedding is enabled and the wrong key is used" (is (= "Message seems corrupt or manipulated" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-card [card (api.pivots/pivot-card)] - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (pivot-card-query-url card)))))))))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (pivot-card-query-url card)))))))))))) (defn- pivot-dashcard-url {:style/indent 1} [dashcard & [additional-token-params]] (str "preview_embed/pivot/dashboard/" @@ -471,7 +471,7 @@ (mt/test-drivers (api.pivots/applicable-drivers) (mt/dataset test-data (testing "GET /api/preview_embed/pivot/dashboard/:token/dashcard/:dashcard-id/card/:card-id" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (embed-test/with-temp-dashcard [dashcard {:dash {:parameters []} :card (api.pivots/pivot-card) :dashcard {:parameter_mappings []}}] @@ -490,17 +490,17 @@ (testing "should fail if embedding is disabled" (is (= "Embedding is not enabled." (mt/with-temporary-setting-values [enable-embedding false] - (embed-test/with-new-secret-key + (embed-test/with-new-secret-key! (mt/user-http-request :crowberto :get 400 (pivot-dashcard-url dashcard))))))) (testing "should fail if embedding is enabled and the wrong key is used" (is (= "Message seems corrupt or manipulated" - (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key (pivot-dashcard-url dashcard)))))))))))) + (mt/user-http-request :crowberto :get 400 (embed-test/with-new-secret-key! (pivot-dashcard-url dashcard)))))))))))) (deftest handle-single-params-for-operator-filters-test (testing "Query endpoints should work with a single URL parameter for an operator filter (#20438)" (mt/dataset test-data - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (t2.with-temp/with-temp [Card {card-id :id, :as card} {:dataset_query (mt/native-query {:query "SELECT count(*) AS count FROM PUBLIC.PEOPLE WHERE true [[AND {{NAME}}]]" :template-tags {"NAME" @@ -545,17 +545,17 @@ (defn sign [claims] (jwt/sign claims *secret-key*)) -(defn do-with-new-secret-key [f] +(defn do-with-new-secret-key! [f] (binding [*secret-key* (random-embedding-secret-key)] (mt/with-temporary-setting-values [embedding-secret-key *secret-key*] (f)))) -(defmacro with-new-secret-key {:style/indent 0} [& body] - `(do-with-new-secret-key (fn [] ~@body))) +(defmacro with-new-secret-key! {:style/indent 0} [& body] + `(do-with-new-secret-key! (fn [] ~@body))) -(defmacro with-embedding-enabled-and-new-secret-key {:style/indent 0} [& body] +(defmacro with-embedding-enabled-and-new-secret-key! {:style/indent 0} [& body] `(mt/with-temporary-setting-values [~'enable-embedding true] - (with-new-secret-key + (with-new-secret-key! ~@body))) (defn dash-token @@ -566,7 +566,7 @@ (deftest params-with-static-list-test (testing "embedding with parameter that has source is a static list" - (with-embedding-enabled-and-new-secret-key + (with-embedding-enabled-and-new-secret-key! (api.dashboard-test/with-chain-filter-fixtures [{:keys [dashboard]}] (t2/update! Dashboard (u/the-id dashboard) {:enable_embedding false ;; works without enabling embedding on the dashboard (#44962) :embedding_params {"static_category" "enabled" @@ -581,7 +581,7 @@ (deftest boolean-parameter-values-test (testing "embedding endpoint supports boolean parameter values (#27643)" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (mt/dataset places-cam-likes (mt/with-temp [:model/Card {card-id :id :as card} {:dataset_query {:database (mt/id) @@ -642,7 +642,7 @@ (deftest string-parameter-values-test (testing "embedding endpoint should not parse string values into numbers (#46240)" - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (mt/dataset airports (mt/with-temp [:model/Card {card-id :id} {:dataset_query {:database (mt/id) diff --git a/test/metabase/api/public_test.clj b/test/metabase/api/public_test.clj index 97d8ba369525b52a1401520c4924fdd90d53fb0d..9247b952f3b2222ae1cd23c8a7b9efd9ed4e6ac1 100644 --- a/test/metabase/api/public_test.clj +++ b/test/metabase/api/public_test.clj @@ -421,7 +421,7 @@ :value nil}])))))))))) ;; Cards with required params -(defn- do-with-required-param-card [f] +(defn- do-with-required-param-card! [f] (mt/with-temporary-setting-values [enable-public-sharing true] (with-temp-public-card [{uuid :public_uuid} {:dataset_query @@ -434,11 +434,11 @@ :required true}}}}}] (f uuid)))) -(defmacro ^:private with-required-param-card [[uuid-binding] & body] - `(do-with-required-param-card (fn [~uuid-binding] ~@body))) +(defmacro ^:private with-required-param-card! [[uuid-binding] & body] + `(do-with-required-param-card! (fn [~uuid-binding] ~@body))) (deftest should-be-able-to-run-a-card-with-a-required-param - (with-required-param-card [uuid] + (with-required-param-card! [uuid] (is (= [[22]] (mt/rows (client/client :get 202 (str "public/card/" uuid "/query") @@ -449,7 +449,7 @@ (deftest missing-required-param-error-message-test (testing (str "If you're missing a required param, the error message should get passed thru, rather than the normal " "generic 'Query Failed' message that we show for most embedding errors") - (with-required-param-card [uuid] + (with-required-param-card! [uuid] (is (= {:status "failed" :error "You'll need to pick a value for 'Price' before this query can run." :error_type "missing-required-parameter"} @@ -903,7 +903,7 @@ (t2/update! DashboardCard (u/the-id dashcard) {:parameter_mappings [{:card_id (u/the-id card) :target ["dimension" dimension]}]})) -(defn- GET-param-values [dashboard] +(defn- GET-param-values! [dashboard] (mt/with-temporary-setting-values [enable-public-sharing true] (:param_values (client/client :get 200 (str "public/dashboard/" (:public_uuid dashboard)))))) @@ -919,21 +919,21 @@ (add-price-param-to-dashboard! dash) (add-dimension-param-mapping-to-dashcard! dashcard card ["template-tag" "price"]) (is (= (price-param-values) - (GET-param-values dash))))) + (GET-param-values! dash))))) (deftest check-that-param-info-comes-back-for-mbql-cards--field-id- (with-temp-public-dashboard-and-card [dash card dashcard] (add-price-param-to-dashboard! dash) (add-dimension-param-mapping-to-dashcard! dashcard card ["field" (mt/id :venues :price) nil]) (is (= (price-param-values) - (GET-param-values dash))))) + (GET-param-values! dash))))) (deftest check-that-param-info-comes-back-for-mbql-cards--fk--- (with-temp-public-dashboard-and-card [dash card dashcard] (add-price-param-to-dashboard! dash) (add-dimension-param-mapping-to-dashcard! dashcard card [:field (mt/id :venues :price) {:source-field (mt/id :checkins :venue_id)}]) (is (= (price-param-values) - (GET-param-values dash))))) + (GET-param-values! dash))))) ;;; +----------------------------------------------------------------------------------------------------------------+ ;;; | New FieldValues search endpoints | @@ -1151,15 +1151,15 @@ "/field/" (u/the-id field-or-id) "/values")) -(defn- do-with-sharing-enabled-and-temp-card-referencing {:style/indent 2} [table-kw field-kw f] +(defn- do-with-sharing-enabled-and-temp-card-referencing! {:style/indent 2} [table-kw field-kw f] (mt/with-temporary-setting-values [enable-public-sharing true] (t2.with-temp/with-temp [Card card (merge (shared-obj) (mbql-card-referencing table-kw field-kw))] (f card)))) -(defmacro ^:private with-sharing-enabled-and-temp-card-referencing +(defmacro ^:private with-sharing-enabled-and-temp-card-referencing! {:style/indent 3} [table-kw field-kw [card-binding] & body] - `(do-with-sharing-enabled-and-temp-card-referencing ~table-kw ~field-kw + `(do-with-sharing-enabled-and-temp-card-referencing! ~table-kw ~field-kw (fn [~card-binding] ~@body))) @@ -1171,24 +1171,24 @@ ["BCD Tofu House"]] :field_id (mt/id :venues :name) :has_more_values false} - (with-sharing-enabled-and-temp-card-referencing :venues :name [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :name [card] (-> (client/client :get 200 (field-values-url card (mt/id :venues :name))) (update :values (partial take 5))))))) (deftest but-for-fields-that-are-not-referenced-we-should-get-an-exception (is (= "Not found." - (with-sharing-enabled-and-temp-card-referencing :venues :name [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :name [card] (client/client :get 404 (field-values-url card (mt/id :venues :price))))))) (deftest field-value-endpoint-should-fail-if-public-sharing-is-disabled (is (= "An error occurred." - (with-sharing-enabled-and-temp-card-referencing :venues :name [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :name [card] (mt/with-temporary-setting-values [enable-public-sharing false] (client/client :get 400 (field-values-url card (mt/id :venues :name)))))))) ;;; ----------------------------- GET /api/public/dashboard/:uuid/field/:field/values nil ----------------------------- -(defn do-with-sharing-enabled-and-temp-dashcard-referencing {:style/indent 2} [table-kw field-kw f] +(defn do-with-sharing-enabled-and-temp-dashcard-referencing! {:style/indent 2} [table-kw field-kw f] (mt/with-temporary-setting-values [enable-public-sharing true] (mt/with-temp [Dashboard dashboard (shared-obj) Card card (mbql-card-referencing table-kw field-kw) @@ -1200,15 +1200,15 @@ (mt/id table-kw field-kw) nil]]}]}] (f dashboard card dashcard)))) -(defmacro with-sharing-enabled-and-temp-dashcard-referencing +(defmacro with-sharing-enabled-and-temp-dashcard-referencing! {:style/indent 3} [table-kw field-kw [dashboard-binding card-binding dashcard-binding] & body] - `(do-with-sharing-enabled-and-temp-dashcard-referencing ~table-kw ~field-kw + `(do-with-sharing-enabled-and-temp-dashcard-referencing! ~table-kw ~field-kw (fn [~(or dashboard-binding '_) ~(or card-binding '_) ~(or dashcard-binding '_)] ~@body))) (deftest should-be-able-to-use-it-when-everything-is-g2g - (with-sharing-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (is (= {:values [["20th Century Cafe"] ["25°"] ["33 Taps"] @@ -1220,12 +1220,12 @@ (update :values (partial take 5))))))) (deftest shound-not-be-able-to-use-the-endpoint-with-a-field-not-referenced-by-the-dashboard - (with-sharing-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (is (= "Not found." (client/client :get 404 (field-values-url dashboard (mt/id :venues :price))))))) (deftest endpoint-should-fail-if-public-sharing-is-disabled - (with-sharing-enabled-and-temp-dashcard-referencing :venues :name [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :name [dashboard] (mt/with-temporary-setting-values [enable-public-sharing false] (is (= "An error occurred." (client/client :get 400 (field-values-url dashboard (mt/id :venues :name)))))))) @@ -1234,18 +1234,18 @@ ;;; ----------------------------------------------- search-card-fields ----------------------------------------------- (deftest search-card-fields - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (is (= [[93 "33 Taps"]] (api.public/search-card-fields (u/the-id card) (mt/id :venues :id) (mt/id :venues :name) "33 T" 10))))) (deftest shouldn-t-work-if-the-search-field-isn-t-allowed-to-be-used-in-combination-with-the-other-field - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (is (thrown? Exception (api.public/search-card-fields (u/the-id card) (mt/id :venues :id) (mt/id :venues :price) "33 T" 10))))) (deftest shouldn-t-work-if-the-field-isn-t-referenced-by-card - (with-sharing-enabled-and-temp-card-referencing :venues :name [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :name [card] (is (thrown? Exception (api.public/search-card-fields (u/the-id card) (mt/id :venues :id) (mt/id :venues :id) "33 T" 10))))) @@ -1264,19 +1264,19 @@ (deftest field-search-with-venue (is (= [[93 "33 Taps"]] - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (client/client :get 200 (field-search-url card (mt/id :venues :id) (mt/id :venues :name)) :value "33 T"))))) (deftest if-search-field-isn-t-allowed-to-be-used-with-the-other-field-endpoint-should-return-exception (is (= "An error occurred." - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (client/client :get 400 (field-search-url card (mt/id :venues :id) (mt/id :venues :price)) :value "33 T"))))) (deftest search-endpoint-should-fail-if-public-sharing-is-disabled (is (= "An error occurred." - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (mt/with-temporary-setting-values [enable-public-sharing false] (client/client :get 400 (field-search-url card (mt/id :venues :id) (mt/id :venues :name)) :value "33 T")))))) @@ -1285,19 +1285,19 @@ ;;; -------------------- GET /api/public/dashboard/:uuid/field/:field/search/:search-field-id nil --------------------- (deftest dashboard - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (is (= [[93 "33 Taps"]] (client/client :get (field-search-url dashboard (mt/id :venues :id) (mt/id :venues :name)) :value "33 T"))))) (deftest dashboard-if-search-field-isn-t-allowed-to-be-used-with-the-other-field-endpoint-should-return-exception - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (is (= "An error occurred." (client/client :get 400 (field-search-url dashboard (mt/id :venues :id) (mt/id :venues :price)) :value "33 T"))))) (deftest dashboard-endpoint-should-fail-if-public-sharing-is-disabled - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (mt/with-temporary-setting-values [enable-public-sharing false] (is (= "An error occurred." (client/client :get 400 (field-search-url dashboard (mt/id :venues :name) (mt/id :venues :name)) @@ -1330,26 +1330,26 @@ (deftest we-should-be-able-to-use-the-api-endpoint-and-get-the-same-results-we-get-by-calling-the-function-above-directly - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (is (= [10 "Fred 62"] (client/client :get 200 (field-remapping-url card (mt/id :venues :id) (mt/id :venues :name)) :value "10"))))) (deftest shouldn-t-work-if-card-doesn-t-reference-the-field-in-question - (with-sharing-enabled-and-temp-card-referencing :venues :price [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :price [card] (is (= "Not found." (client/client :get 404 (field-remapping-url card (mt/id :venues :id) (mt/id :venues :name)) :value "10"))))) (deftest ---or-if-the-remapping-field-isn-t-allowed-to-be-used-with-the-other-field - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (is (= "An error occurred." (client/client :get 400 (field-remapping-url card (mt/id :venues :id) (mt/id :venues :price)) :value "10"))))) (deftest ---or-if-public-sharing-is-disabled - (with-sharing-enabled-and-temp-card-referencing :venues :id [card] + (with-sharing-enabled-and-temp-card-referencing! :venues :id [card] (mt/with-temporary-setting-values [enable-public-sharing false] (is (= "An error occurred." (client/client :get 400 (field-remapping-url card (mt/id :venues :id) (mt/id :venues :name)) @@ -1359,25 +1359,25 @@ (deftest api-endpoint-should-return-same-results-as-function - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (is (= [10 "Fred 62"] (client/client :get 200 (field-remapping-url dashboard (mt/id :venues :id) (mt/id :venues :name)) :value "10"))))) (deftest field-remapping-shouldn-t-work-if-card-doesn-t-reference-the-field-in-question - (with-sharing-enabled-and-temp-dashcard-referencing :venues :price [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :price [dashboard] (is (= "Not found." (client/client :get 404 (field-remapping-url dashboard (mt/id :venues :id) (mt/id :venues :name)) :value "10"))))) (deftest remapping-or-if-the-remapping-field-isn-t-allowed-to-be-used-with-the-other-field - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (is (= "An error occurred." (client/client :get 400 (field-remapping-url dashboard (mt/id :venues :id) (mt/id :venues :price)) :value "10"))))) (deftest remapping-or-if-public-sharing-is-disabled - (with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (mt/with-temporary-setting-values [enable-public-sharing false] (is (= "An error occurred." (client/client :get 400 (field-remapping-url dashboard (mt/id :venues :id) (mt/id :venues :name)) diff --git a/test/metabase/api/pulse_test.clj b/test/metabase/api/pulse_test.clj index fbedd7d1a74f251d91799108575b6b7e7fa638cf..8247fb318b97051298f1e485d642e8617f15c3d6 100644 --- a/test/metabase/api/pulse_test.clj +++ b/test/metabase/api/pulse_test.clj @@ -71,7 +71,7 @@ (update :cards #(for [card %] (update card :collection_id boolean))))) -(defn- do-with-pulses-in-a-collection [grant-collection-perms-fn! pulses-or-ids f] +(defn- do-with-pulses-in-a-collection! [grant-collection-perms-fn! pulses-or-ids f] (mt/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection collection] (grant-collection-perms-fn! (perms-group/all-users) collection) @@ -82,14 +82,14 @@ :where [:in :id (set (map u/the-id pulses-or-ids))]})) (f)))) -(defmacro ^:private with-pulses-in-nonreadable-collection [pulses-or-ids & body] - `(do-with-pulses-in-a-collection (constantly nil) ~pulses-or-ids (fn [] ~@body))) +(defmacro ^:private with-pulses-in-nonreadable-collection! [pulses-or-ids & body] + `(do-with-pulses-in-a-collection! (constantly nil) ~pulses-or-ids (fn [] ~@body))) -(defmacro ^:private with-pulses-in-readable-collection [pulses-or-ids & body] - `(do-with-pulses-in-a-collection perms/grant-collection-read-permissions! ~pulses-or-ids (fn [] ~@body))) +(defmacro ^:private with-pulses-in-readable-collection! [pulses-or-ids & body] + `(do-with-pulses-in-a-collection! perms/grant-collection-read-permissions! ~pulses-or-ids (fn [] ~@body))) -(defmacro ^:private with-pulses-in-writeable-collection [pulses-or-ids & body] - `(do-with-pulses-in-a-collection perms/grant-collection-readwrite-permissions! ~pulses-or-ids (fn [] ~@body))) +(defmacro ^:private with-pulses-in-writeable-collection! [pulses-or-ids & body] + `(do-with-pulses-in-a-collection! perms/grant-collection-readwrite-permissions! ~pulses-or-ids (fn [] ~@body))) ;;; +----------------------------------------------------------------------------------------------------------------+ @@ -184,7 +184,7 @@ Card card-2 {} Dashboard _ {:name "Birdcage KPIs"} Collection collection {}] - (api.card-test/with-cards-in-readable-collection [card-1 card-2] + (api.card-test/with-cards-in-readable-collection! [card-1 card-2] (mt/with-model-cleanup [Pulse] (is (= (merge pulse-defaults @@ -236,7 +236,7 @@ :dashboard_id permitted-dashboard-id :skip_if_empty false :parameters filter-params}] - (api.card-test/with-cards-in-readable-collection [card-1 card-2] + (api.card-test/with-cards-in-readable-collection! [card-1 card-2] (mt/with-model-cleanup [Pulse] (testing "successful creation" (is (= (merge @@ -269,7 +269,7 @@ Card card-2 {:name "The card" :description "Info" :display :table}] - (api.card-test/with-cards-in-readable-collection [card-1 card-2] + (api.card-test/with-cards-in-readable-collection! [card-1 card-2] (t2.with-temp/with-temp [Collection collection] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (mt/with-model-cleanup [Pulse] @@ -311,7 +311,7 @@ (t2.with-temp/with-temp [Collection collection] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (mt/with-model-cleanup [Pulse] - (api.card-test/with-cards-in-readable-collection [card-1 card-2] + (api.card-test/with-cards-in-readable-collection! [card-1 card-2] (is (= (merge pulse-defaults {:name "A Pulse" @@ -363,7 +363,7 @@ (let [pulse-name (mt/random-name)] (mt/with-temp [Card card {} Collection collection] {} - (api.card-test/with-cards-in-readable-collection [card] + (api.card-test/with-cards-in-readable-collection! [card] (create-pulse! 200 pulse-name card collection) (is (= {:collection_id (u/the-id collection), :collection_position 1} (mt/derecordize (t2/select-one [Pulse :collection_id :collection_position] :name pulse-name))))))) @@ -424,8 +424,8 @@ PulseChannelRecipient _ {:pulse_channel_id (u/the-id pc) :user_id (mt/user->id :rasta)} Card card {}] (let [filter-params [{:id "123abc", :name "species", :type "string"}]] - (with-pulses-in-writeable-collection [pulse] - (api.card-test/with-cards-in-readable-collection [card] + (with-pulses-in-writeable-collection! [pulse] + (api.card-test/with-cards-in-readable-collection! [card] (is (= (merge pulse-defaults {:name "Updated Pulse" @@ -470,8 +470,8 @@ :pulse_id (u/the-id pulse)} Card card-2 {:name "Test2" :description "Just Testing2"}] - (with-pulses-in-writeable-collection [pulse] - (api.card-test/with-cards-in-readable-collection [card-1 card-2] + (with-pulses-in-writeable-collection! [pulse] + (api.card-test/with-cards-in-readable-collection! [card-1 card-2] ;; The FE will include the original HybridPulseCard, similar to how the API returns the card via GET (let [pulse-cards (:cards (mt/user-http-request :rasta :get 200 (format "pulse/%d" (u/the-id pulse))))] (is (= (merge @@ -502,7 +502,7 @@ (deftest change-collection-test (testing "Can we change the Collection a Pulse is in (assuming we have the permissions to do so)?" - (pulse-test/with-pulse-in-collection [_db collection pulse] + (pulse-test/with-pulse-in-collection! [_db collection pulse] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for both new and old collections (doseq [coll [collection new-collection]] @@ -514,7 +514,7 @@ (u/the-id new-collection))))) (testing "...but if we don't have the Permissions for the old collection, we should get an Exception" - (pulse-test/with-pulse-in-collection [_db _collection pulse] + (pulse-test/with-pulse-in-collection! [_db _collection pulse] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for only the *new* collection (perms/grant-collection-readwrite-permissions! (perms-group/all-users) new-collection) @@ -523,7 +523,7 @@ (mt/user-http-request :rasta :put 403 (str "pulse/" (u/the-id pulse)) {:collection_id (u/the-id new-collection)})))))) (testing "...and if we don't have the Permissions for the new collection, we should get an Exception" - (pulse-test/with-pulse-in-collection [_db collection pulse] + (pulse-test/with-pulse-in-collection! [_db collection pulse] (t2.with-temp/with-temp [Collection new-collection] ;; grant Permissions for only the *old* collection (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) @@ -533,7 +533,7 @@ (deftest update-collection-position-test (testing "Can we change the Collection position of a Pulse?" - (pulse-test/with-pulse-in-collection [_ collection pulse] + (pulse-test/with-pulse-in-collection! [_ collection pulse] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (mt/user-http-request :rasta :put 200 (str "pulse/" (u/the-id pulse)) {:collection_position 1}) @@ -541,7 +541,7 @@ (t2/select-one-fn :collection_position Pulse :id (u/the-id pulse))))) (testing "...and unset (unpin) it as well?" - (pulse-test/with-pulse-in-collection [_ collection pulse] + (pulse-test/with-pulse-in-collection! [_ collection pulse] (t2/update! Pulse (u/the-id pulse) {:collection_position 1}) (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (mt/user-http-request :rasta :put 200 (str "pulse/" (u/the-id pulse)) @@ -550,7 +550,7 @@ (t2/select-one-fn :collection_position Pulse :id (u/the-id pulse)))))) (testing "...we shouldn't be able to if we don't have permissions for the Collection" - (pulse-test/with-pulse-in-collection [_db _collection pulse] + (pulse-test/with-pulse-in-collection! [_db _collection pulse] (mt/user-http-request :rasta :put 403 (str "pulse/" (u/the-id pulse)) {:collection_position 1}) (is (= nil @@ -565,7 +565,7 @@ (deftest archive-test (testing "Can we archive a Pulse?" - (pulse-test/with-pulse-in-collection [_ collection pulse] + (pulse-test/with-pulse-in-collection! [_ collection pulse] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (mt/user-http-request :rasta :put 200 (str "pulse/" (u/the-id pulse)) {:archived true}) @@ -574,7 +574,7 @@ (deftest unarchive-test (testing "Can we unarchive a Pulse?" - (pulse-test/with-pulse-in-collection [_ collection pulse] + (pulse-test/with-pulse-in-collection! [_ collection pulse] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) (t2/update! Pulse (u/the-id pulse) {:archived true}) (mt/user-http-request :rasta :put 200 (str "pulse/" (u/the-id pulse)) @@ -768,7 +768,7 @@ PulseChannel pc {:pulse_id pulse-3-id} PulseChannelRecipient _ {:pulse_channel_id (u/the-id pc) :user_id (mt/user->id :rasta)}] - (with-pulses-in-writeable-collection [pulse-1 pulse-2 pulse-3] + (with-pulses-in-writeable-collection! [pulse-1 pulse-2 pulse-3] (testing "admins can see all pulses" (let [results (-> (mt/user-http-request :crowberto :get 200 "pulse") (filter-pulse-results :id #{pulse-1-id pulse-2-id pulse-3-id}))] @@ -807,7 +807,7 @@ (assoc (expected-pulse-shape pulse-3) :can_write false)] (map #(update % :collection_id boolean) results))))))) - (with-pulses-in-nonreadable-collection [pulse-3] + (with-pulses-in-nonreadable-collection! [pulse-3] (testing "when `creator_or_recipient=true`, cards and recipients are not included in results if the user does not have collection perms" (let [result (-> (mt/user-http-request :rasta :get 200 "pulse?creator_or_recipient=true") @@ -821,7 +821,7 @@ Pulse pulse-2 {:name "GHIJKL"} Pulse pulse-3 {:name "AAAAAA" :alert_condition "rows"}] - (with-pulses-in-readable-collection [pulse-1 pulse-2 pulse-3] + (with-pulses-in-readable-collection! [pulse-1 pulse-2 pulse-3] (is (= [(assoc (pulse-details pulse-1) :can_write true, :collection_id true) (assoc (pulse-details pulse-2) :can_write true, :collection_id true)] (for [pulse (-> (mt/user-http-request :rasta :get 200 "pulse") @@ -831,7 +831,7 @@ (testing "by default, archived Pulses should be excluded" (mt/with-temp [Pulse not-archived-pulse {:name "Not Archived"} Pulse archived-pulse {:name "Archived" :archived true}] - (with-pulses-in-readable-collection [not-archived-pulse archived-pulse] + (with-pulses-in-readable-collection! [not-archived-pulse archived-pulse] (is (= #{"Not Archived"} (set (map :name (-> (mt/user-http-request :rasta :get 200 "pulse") (filter-pulse-results :name #{"Not Archived" "Archived"}))))))))) @@ -839,7 +839,7 @@ (testing "can we fetch archived Pulses?" (mt/with-temp [Pulse not-archived-pulse {:name "Not Archived"} Pulse archived-pulse {:name "Archived" :archived true}] - (with-pulses-in-readable-collection [not-archived-pulse archived-pulse] + (with-pulses-in-readable-collection! [not-archived-pulse archived-pulse] (is (= #{"Archived"} (set (map :name (-> (mt/user-http-request :rasta :get 200 "pulse?archived=true") (filter-pulse-results :name #{"Not Archived" "Archived"}))))))))) @@ -853,7 +853,7 @@ (deftest get-pulse-test (testing "GET /api/pulse/:id" (t2.with-temp/with-temp [Pulse pulse] - (with-pulses-in-readable-collection [pulse] + (with-pulses-in-readable-collection! [pulse] (is (= (assoc (pulse-details pulse) :can_write true :collection_id true) @@ -862,19 +862,19 @@ (testing "cannot normally fetch a pulse without collection permissions" (t2.with-temp/with-temp [Pulse pulse {:creator_id (mt/user->id :crowberto)}] - (with-pulses-in-nonreadable-collection [pulse] + (with-pulses-in-nonreadable-collection! [pulse] (mt/user-http-request :rasta :get 403 (str "pulse/" (u/the-id pulse)))))) (testing "can fetch a pulse without collection permissions if you are the creator or a recipient" (t2.with-temp/with-temp [Pulse pulse {:creator_id (mt/user->id :rasta)}] - (with-pulses-in-nonreadable-collection [pulse] + (with-pulses-in-nonreadable-collection! [pulse] (mt/user-http-request :rasta :get 200 (str "pulse/" (u/the-id pulse))))) (mt/with-temp [Pulse pulse {:creator_id (mt/user->id :crowberto)} PulseChannel pc {:pulse_id (u/the-id pulse)} PulseChannelRecipient _ {:pulse_channel_id (u/the-id pc) :user_id (mt/user->id :rasta)}] - (with-pulses-in-nonreadable-collection [pulse] + (with-pulses-in-nonreadable-collection! [pulse] (mt/user-http-request :rasta :get 200 (str "pulse/" (u/the-id pulse)))))))) (deftest send-test-pulse-test @@ -893,7 +893,7 @@ :default 3}]} Card card {:dataset_query (mt/mbql-query incidents {:aggregation [[:count]]})}] (perms/grant-collection-readwrite-permissions! (perms-group/all-users) collection) - (api.card-test/with-cards-in-readable-collection [card] + (api.card-test/with-cards-in-readable-collection! [card] (let [channel-messages (pulse.test-util/with-captured-channel-send-messages! (is (= {:ok true} (mt/user-http-request :rasta :post 200 "pulse/test" diff --git a/test/metabase/api/revision_test.clj b/test/metabase/api/revision_test.clj index 30eeb638b75a7ad870d5542778ec295d3c7cd6f9..45660b0089835209ec25d145e32ac126df4e5cb2 100644 --- a/test/metabase/api/revision_test.clj +++ b/test/metabase/api/revision_test.clj @@ -431,7 +431,7 @@ (mt/user-http-request :crowberto :get 200 "revision" :entity "card" :id card-id))))))) (deftest revision-descriptions-are-i18ned-test - (mt/with-mock-i18n-bundles {"fr" {:messages {"created this" "créé ceci" + (mt/with-mock-i18n-bundles! {"fr" {:messages {"created this" "créé ceci" "added a description" "ajouté une description" "renamed {0} from \"{1}\" to \"{2}\"" "renommé {0} de {1} à {2}" "this {0}" "ce {0}" diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj index 0d3e1bbb3c5f8f6769cb0caa1f7da0244dd026a3..e33e31529eeea10f54184467f220e36baab70a93 100644 --- a/test/metabase/api/session_test.clj +++ b/test/metabase/api/session_test.clj @@ -466,7 +466,7 @@ (reset-throttlers!) (testing "GET /session/properties" (testing "Setting the X-Metabase-Locale header should result give you properties in that locale" - (mt/with-mock-i18n-bundles {"es" {:messages {"Connection String" "Cadena de conexión !"}}} + (mt/with-mock-i18n-bundles! {"es" {:messages {"Connection String" "Cadena de conexión !"}}} (is (= "Cadena de conexión !" (-> (mt/client :get 200 "session/properties" {:request-options {:headers {"x-metabase-locale" "es"}}}) :engines :h2 :details-fields first :display-name))))))) @@ -516,7 +516,7 @@ (deftest ldap-login-test (reset-throttlers!) - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "Test that we can login with LDAP" (t2.with-temp/with-temp [User _ {:email "ngoc@metabase.com" :password "securedpassword"}] diff --git a/test/metabase/api/setting_test.clj b/test/metabase/api/setting_test.clj index c55cd069821ec7841a1cb8d2d5f5915a6808e6f0..79e5e7b909c66298083883fd63dce18d0183d29e 100644 --- a/test/metabase/api/setting_test.clj +++ b/test/metabase/api/setting_test.clj @@ -55,16 +55,16 @@ ([user setting-name status] (mt/user-http-request user :get status (format "setting/%s" (name setting-name))))) -(defn- do-with-mocked-settings-manager-access +(defn- do-with-mocked-settings-manager-access! [f] (with-redefs [setting/has-advanced-setting-access? (constantly true) validation/check-has-application-permission (constantly true)] (f))) -(defmacro ^:private with-mocked-settings-manager-access +(defmacro ^:private with-mocked-settings-manager-access! "Runs `body` with the approrpiate functions redefined to give the current user settings manager permissions." [& body] - `(do-with-mocked-settings-manager-access (fn [] ~@body))) + `(do-with-mocked-settings-manager-access! (fn [] ~@body))) (deftest fetch-setting-test (testing "GET /api/setting" @@ -88,7 +88,7 @@ (testing "Check that non-admin setting managers can fetch Settings with `:visibility :settings-manager`" (test-settings-manager-visibility! nil) - (with-mocked-settings-manager-access + (with-mocked-settings-manager-access! (is (= [{:key "test-settings-manager-visibility", :value nil, :is_env_setting false, @@ -109,7 +109,7 @@ (testing "Test that non-admin setting managers can fetch a single Setting if it has `:visibility :settings-manager`." (test-settings-manager-visibility! "OK!") - (with-mocked-settings-manager-access + (with-mocked-settings-manager-access! (is (= "OK!" (fetch-setting :test-settings-manager-visibility 200))))) (testing "Check that non-superusers cannot fetch a single Setting if it is not user-local" @@ -166,7 +166,7 @@ "Updated setting should be visible from API endpoint") (testing "Check that non-admin setting managers can only update Settings with `:visibility :settings-manager`." - (with-mocked-settings-manager-access + (with-mocked-settings-manager-access! (mt/user-http-request :rasta :put 204 "setting/test-settings-manager-visibility" {:value "NICE!"}) (is (= "NICE!" (fetch-setting :test-settings-manager-visibility 200))) @@ -304,7 +304,7 @@ (models.setting-test/test-setting-2)))) (testing "non-admin setting managers should only be able to update multiple settings at once if they have `:visibility :settings-manager`" - (with-mocked-settings-manager-access + (with-mocked-settings-manager-access! (is (= nil (mt/user-http-request :rasta :put 204 "setting" {:test-settings-manager-visibility "ABC"}))) (is (= "ABC" diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj index 7b3c6c9b1c49bbe823cf6a21b2d44fba5119c5b8..bea3ff0fb35747cc6317798dd628d715433dd75f 100644 --- a/test/metabase/api/user_test.clj +++ b/test/metabase/api/user_test.clj @@ -918,16 +918,16 @@ (client/client creds :put 403 (format "user/%d" (u/the-id user)) {:email "adifferentemail@metabase.com"})))))))) -(defn- do-with-preserved-rasta-personal-collection-name [thunk] +(defn- do-with-preserved-rasta-personal-collection-name! [thunk] (let [{collection-name :name, :keys [slug id]} (collection/user->personal-collection (mt/user->id :rasta))] (mt/with-temp-vals-in-db Collection id {:name collection-name, :slug slug} (thunk)))) -(defmacro ^:private with-preserved-rasta-personal-collection-name +(defmacro ^:private with-preserved-rasta-personal-collection-name! "Preserve the name of Rasta's personal collection inside a body that might cause it to change (e.g. changing user name via the API.)" [& body] - `(do-with-preserved-rasta-personal-collection-name (fn [] ~@body))) + `(do-with-preserved-rasta-personal-collection-name! (fn [] ~@body))) (deftest update-groups-test (testing "PUT /api/user/:id" @@ -943,7 +943,7 @@ ;; By wrapping the test in this macro even if the test fails it will restore the original values (mt/with-temp-vals-in-db User (mt/user->id :rasta) {:first_name "Rasta"} (mt/test-helpers-set-global-values! - (with-preserved-rasta-personal-collection-name + (with-preserved-rasta-personal-collection-name! (t2.with-temp/with-temp [PermissionsGroup group {:name "Blue Man Group"}] (mt/user-http-request :rasta :put 403 (str "user/" (mt/user->id :rasta)) {:user_group_memberships (group-or-ids->user-group-memberships [(perms-group/all-users) group]) @@ -957,7 +957,7 @@ (testing "if we pass user_group_memberships as a non-superuser the call should succeed, so long as the value doesn't change" (mt/with-temp-vals-in-db User (mt/user->id :rasta) {:first_name "Rasta"} - (with-preserved-rasta-personal-collection-name + (with-preserved-rasta-personal-collection-name! (mt/user-http-request :rasta :put 200 (str "user/" (mt/user->id :rasta)) {:user_group_memberships (group-or-ids->user-group-memberships [(perms-group/all-users)]) :first_name "Reggae"})) diff --git a/test/metabase/async/streaming_response_test.clj b/test/metabase/async/streaming_response_test.clj index a2b3fb251dd2d42486c0c48502272e579b15611a..c75d6f070a43e6bc37a6597e8c32ed55930f2118 100644 --- a/test/metabase/async/streaming_response_test.clj +++ b/test/metabase/async/streaming_response_test.clj @@ -27,7 +27,7 @@ (def ^:private thread-pool-size 5) -(defn- do-with-streaming-response-thread-pool [thunk] +(defn- do-with-streaming-response-thread-pool! [thunk] (let [pool (Executors/newFixedThreadPool thread-pool-size (.build (doto (BasicThreadFactory$Builder.) @@ -40,13 +40,13 @@ (finally (.shutdownNow pool)))))) -(defmacro ^:private with-streaming-response-thread-pool {:style/indent 0} [& body] - `(do-with-streaming-response-thread-pool (fn [] ~@body))) +(defmacro ^:private with-streaming-response-thread-pool! {:style/indent 0} [& body] + `(do-with-streaming-response-thread-pool! (fn [] ~@body))) (defmacro ^:private with-test-driver-db! {:style/indent 0} [& body] `(t2.with-temp/with-temp [Database db# {:engine ::test-driver}] (mt/with-db db# - (with-streaming-response-thread-pool + (with-streaming-response-thread-pool! ~@body)))) (def ^:private start-execution-chan diff --git a/test/metabase/db/custom_migrations_test.clj b/test/metabase/db/custom_migrations_test.clj index f5d0946ce001fa4d10e4c3b2aef9eea12c40f062..8aca5dbdfd67c9dbd02550a5b70ca3cb71a7e4f7 100644 --- a/test/metabase/db/custom_migrations_test.clj +++ b/test/metabase/db/custom_migrations_test.clj @@ -1466,7 +1466,7 @@ [setting-k] (json/parse-string (t2/select-one-fn :value :setting :key (name setting-k)))) -(defn- call-with-ldap-and-sso-configured [ldap-group-mappings sso-group-mappings f] +(defn- call-with-ldap-and-sso-configured! [ldap-group-mappings sso-group-mappings f] (mt/with-temporary-raw-setting-values [ldap-group-mappings (json/generate-string ldap-group-mappings) saml-group-mappings (json/generate-string sso-group-mappings) @@ -1476,11 +1476,11 @@ jwt-enabled "true"] (f))) -(defmacro ^:private with-ldap-and-sso-configured +(defmacro ^:private with-ldap-and-sso-configured! "Run body with ldap and SSO configured, in which SSO will only be configured if enterprise is available" [ldap-group-mappings sso-group-mappings & body] (binding [setting/*allow-retired-setting-names* true] - `(call-with-ldap-and-sso-configured ~ldap-group-mappings ~sso-group-mappings (fn [] ~@body)))) + `(call-with-ldap-and-sso-configured! ~ldap-group-mappings ~sso-group-mappings (fn [] ~@body)))) ;; The `remove-admin-from-group-mapping-if-needed` migration is written to run in OSS version ;; even though it might make changes to some enterprise-only settings. @@ -1498,14 +1498,14 @@ ldap-expected-mapping {"dc=metabase,dc=com" [(+ 1 admin-group-id)]}] (testing "Remove admin from group mapping for LDAP, SAML, JWT if they are enabled" - (with-ldap-and-sso-configured ldap-group-mappings sso-group-mappings + (with-ldap-and-sso-configured! ldap-group-mappings sso-group-mappings (#'custom-migrations/migrate-remove-admin-from-group-mapping-if-needed) (is (= ldap-expected-mapping (get-json-setting :ldap-group-mappings))) (is (= sso-expected-mapping (get-json-setting :jwt-group-mappings))) (is (= sso-expected-mapping (get-json-setting :saml-group-mappings))))) (testing "remove admin from group mapping for LDAP, SAML, JWT even if they are disabled" - (with-ldap-and-sso-configured ldap-group-mappings sso-group-mappings + (with-ldap-and-sso-configured! ldap-group-mappings sso-group-mappings (mt/with-temporary-raw-setting-values [ldap-enabled "false" saml-enabled "false" @@ -1516,7 +1516,7 @@ (is (= sso-expected-mapping (get-json-setting :saml-group-mappings)))))) (testing "Don't remove admin group if `ldap-sync-admin-group` is enabled" - (with-ldap-and-sso-configured ldap-group-mappings sso-group-mappings + (with-ldap-and-sso-configured! ldap-group-mappings sso-group-mappings (mt/with-temporary-raw-setting-values [ldap-sync-admin-group "true"] (#'custom-migrations/migrate-remove-admin-from-group-mapping-if-needed) @@ -1678,7 +1678,7 @@ (testing "We should delete the triggers for DBs that are configured not to scan their field values\n" (impl/test-migrations "v49.2024-04-09T10:00:03" [migrate!] (letfn [(do-test [] - (api.database-test/with-db-scheduler-setup + (api.database-test/with-db-scheduler-setup! (let [db-with-full-schedules (new-instance-with-default :metabase_database {:metadata_sync_schedule "0 0 * * * ? *" :cache_field_values_schedule "0 0 1 * * ? *" diff --git a/test/metabase/driver/common/parameters/values_test.clj b/test/metabase/driver/common/parameters/values_test.clj index 76f6300e6256fc444e26e2ff09d802eb70917148..b32f1d7e923d5dbc194abe11a1722cbc16706e13 100644 --- a/test/metabase/driver/common/parameters/values_test.clj +++ b/test/metabase/driver/common/parameters/values_test.clj @@ -383,7 +383,7 @@ (testing "Persisted Models are substituted" (mt/test-driver :postgres (mt/dataset test-data - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (let [mbql-query (mt/mbql-query categories)] (mt/with-temp [Card model {:name "model" :type :model diff --git a/test/metabase/driver/common_test.clj b/test/metabase/driver/common_test.clj index 940ac5f77e85f46886c6f9b9bcaa180f2822ebdf..7c7a8723556825a537e33cd12aec97448ab1ab5c 100644 --- a/test/metabase/driver/common_test.clj +++ b/test/metabase/driver/common_test.clj @@ -40,17 +40,17 @@ [4 5 6]))) @realized-lazy-seq?]))))) -(defn- test-start-of-week-offset +(defn- test-start-of-week-offset! [db-start-of-week target-start-of-week] (with-redefs [driver/db-start-of-week (constantly db-start-of-week) setting/get-value-of-type (constantly target-start-of-week)] (driver.common/start-of-week-offset :sql))) (deftest start-of-week-offset-test - (is (= 0 (test-start-of-week-offset :sunday :sunday))) - (is (= -1 (test-start-of-week-offset :sunday :monday))) - (is (= 1 (test-start-of-week-offset :monday :sunday))) - (is (= 5 (test-start-of-week-offset :monday :wednesday)))) + (is (= 0 (test-start-of-week-offset! :sunday :sunday))) + (is (= -1 (test-start-of-week-offset! :sunday :monday))) + (is (= 1 (test-start-of-week-offset! :monday :sunday))) + (is (= 5 (test-start-of-week-offset! :monday :wednesday)))) (deftest cloud-ip-address-info-test (testing "The cloud-ip-address-info field is correctly resolved when fetching driver connection properties" diff --git a/test/metabase/driver/postgres_test.clj b/test/metabase/driver/postgres_test.clj index 6049268ae7db00bc51d3831b197f6b619ea52a10..9304c19d4cef31ed26764c8e6148786afd8e672a 100644 --- a/test/metabase/driver/postgres_test.clj +++ b/test/metabase/driver/postgres_test.clj @@ -1346,7 +1346,7 @@ (driver/syncable-schemas driver/*driver* (mt/db)))))) (testing "metabase_cache schemas should be excluded" (mt/dataset test-data - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (let [conn-spec (sql-jdbc.conn/db->pooled-connection-spec (mt/db))] (mt/with-temp [:model/Card _ {:name "model" :type :model diff --git a/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj b/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj index 4560132b5e06345a84a8ce89e95ea0ade4fbb604..f668f2d9725b41d38082d2d0c57734393ea4f664 100644 --- a/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj +++ b/test/metabase/driver/sql_jdbc/sync/describe_database_test.clj @@ -93,7 +93,7 @@ {:name "REVIEWS", :schema "PUBLIC", :description nil}}} (sql-jdbc.describe-database/describe-database :h2 (mt/id))))) -(defn- describe-database-with-open-resultset-count +(defn- describe-database-with-open-resultset-count! "Just like `describe-database`, but instead of returning the database description returns the number of ResultSet objects the sync process left open. Make sure you wrap ResultSets with `with-open`! Otherwise some JDBC drivers like Oracle and Redshift will keep open cursors indefinitely." @@ -142,7 +142,7 @@ (testing (str "make sure that running the sync process doesn't leak cursors because it's not closing the ResultSets. " "See issues #4389, #6028, and #6467 (Oracle) and #7609 (Redshift)") (is (= 0 - (describe-database-with-open-resultset-count driver/*driver* (mt/db))))))) + (describe-database-with-open-resultset-count! driver/*driver* (mt/db))))))) (defn- sync-and-assert-filtered-tables [database assert-table-fn] (t2.with-temp/with-temp [Database db-filtered database] diff --git a/test/metabase/email/messages_test.clj b/test/metabase/email/messages_test.clj index 54b849da621d1781ab191e0b13198be885996142..acc7c184ede85561d12583e35cd0963fcc461557 100644 --- a/test/metabase/email/messages_test.clj +++ b/test/metabase/email/messages_test.clj @@ -60,7 +60,7 @@ (get-in [0 :body 0 :content]) (str/includes? "deactivated")))))) -(defmacro ^:private with-create-temp-failure [& body] +(defmacro ^:private with-create-temp-failure! [& body] `(with-redefs [messages/create-temp-file (fn [~'_] (throw (IOException. "Failed to write file")))] ~@body)) @@ -70,7 +70,7 @@ (is (thrown-with-msg? IOException (re-pattern (format "Unable to create temp file in `%s`" (System/getProperty "java.io.tmpdir"))) - (with-create-temp-failure + (with-create-temp-failure! (#'messages/create-temp-file-or-throw "txt"))))) (deftest alert-schedule-text-test diff --git a/test/metabase/email_test.clj b/test/metabase/email_test.clj index b4ba1917014fb5f82f2bef426cf5ac291940dca2..44004c81b9754dbcfd6a283b9cfe7acdf667dd60 100644 --- a/test/metabase/email_test.clj +++ b/test/metabase/email_test.clj @@ -69,7 +69,7 @@ [n & body] `(do-with-expected-messages ~n (fn [] ~@body))) -(defn do-with-fake-inbox +(defn do-with-fake-inbox! "Impl for `with-fake-inbox` macro; prefer using that rather than calling this directly." [f] (with-redefs [email/send-email! fake-inbox-email-fn] @@ -78,6 +78,8 @@ email-smtp-port 587] (f)))) +;;; TODO -- rename to `with-fake-inbox!` since it's not thread-safe and remove the Kondo ignore below. +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-fake-inbox "Clear `inbox`, bind `send-email!` to `fake-inbox-email-fn`, set temporary settings for `email-smtp-username` and `email-smtp-password` (which will cause `metabase.email/email-configured?` to return `true`, and execute `body`. @@ -89,7 +91,7 @@ @inbox)" [& body] {:style/indent 0} - `(do-with-fake-inbox (fn [] ~@body))) + `(do-with-fake-inbox! (fn [] ~@body))) (defn- create-email-body->regex-fn "Returns a function expecting the email body structure. It will apply the regexes in `regex-seq` over the body and diff --git a/test/metabase/events/view_log_test.clj b/test/metabase/events/view_log_test.clj index 93e86de5449086d8079fab31ce6cd4b6eb0c935b..106660a83481dfe7bc51aa71e1ae5ea9b531c9d9 100644 --- a/test/metabase/events/view_log_test.clj +++ b/test/metabase/events/view_log_test.clj @@ -265,7 +265,7 @@ (deftest get-embedded-card-embedding-logs-view-test (mt/with-premium-features #{:audit-app} (testing "Viewing an embedding logs the correct view log event." - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (mt/with-temp [:model/Card card {:enable_embedding true}] (testing "GET /api/embed/card/:token" (client/client :get 200 (embed-test/card-url card)) @@ -275,7 +275,7 @@ (deftest embedded-dashboard-card-query-view-log-test (mt/with-premium-features #{:audit-app} (testing "Running a query for a card in a public dashboard logs the correct view log event." - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (mt/with-temp [:model/Dashboard dash {:enable_embedding true} :model/Card card {} :model/DashboardCard dashcard {:dashboard_id (:id dash) @@ -288,7 +288,7 @@ (deftest get-embedded-dashboard-logs-view-test (mt/with-premium-features #{:audit-app} (testing "Viewing an embedding logs the correct view log event." - (embed-test/with-embedding-enabled-and-new-secret-key + (embed-test/with-embedding-enabled-and-new-secret-key! (mt/with-temp [:model/Dashboard dash {:enable_embedding true}] (testing "GET /api/embed/dashboard/:token" (client/client :get 200 (embed-test/dashboard-url dash)) diff --git a/test/metabase/integrations/google_test.clj b/test/metabase/integrations/google_test.clj index c2c999a63b618db143757a610bfa150cd6596b56..d92e439124cf502f9a86cb82609d87283f000008 100644 --- a/test/metabase/integrations/google_test.clj +++ b/test/metabase/integrations/google_test.clj @@ -39,12 +39,12 @@ ;;; --------------------------------------------- account autocreation ----------------------------------------------- -(defmacro ^:private with-no-sso-google-token [& body] +(defmacro ^:private with-no-sso-google-token! [& body] `(with-redefs [premium-features/enable-sso-google? (constantly false)] ~@body)) (deftest allow-autocreation-test - (with-no-sso-google-token + (with-no-sso-google-token! (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"] (are [allowed? email] (= allowed? (#'google/autocreate-user-allowed-for-email? email)) @@ -53,14 +53,14 @@ (deftest google-auth-auto-create-accounts-domain-test (testing "multiple domains cannot be set if EE `:sso-google` feature flag is not enabled" - (with-no-sso-google-token + (with-no-sso-google-token! (is (thrown? clojure.lang.ExceptionInfo (google.i/google-auth-auto-create-accounts-domain! "metabase.com, example.com")))))) (deftest google-auth-create-new-user!-test (mt/with-model-cleanup [User] - (with-no-sso-google-token + (with-no-sso-google-token! (testing "shouldn't be allowed to create a new user via Google Auth if their email doesn't match the auto-create accounts domain" (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain "sf-toucannery.com"] (is (thrown? @@ -136,7 +136,7 @@ (deftest google-auth-fetch-or-create-user!-test (mt/with-model-cleanup [User] - (with-no-sso-google-token + (with-no-sso-google-token! (testing "test that an existing user can log in with Google auth even if the auto-create accounts domain is different from" (t2.with-temp/with-temp [User _ {:email "cam@sf-toucannery.com"}] (mt/with-temporary-setting-values [google-auth-auto-create-accounts-domain "metabase.com"] diff --git a/test/metabase/integrations/ldap_test.clj b/test/metabase/integrations/ldap_test.clj index 8b773e071a24137a166910880cf43a81c9cf1708..cfc1653047bf3244f7e46a509c3dd32fe3f0d995 100644 --- a/test/metabase/integrations/ldap_test.clj +++ b/test/metabase/integrations/ldap_test.clj @@ -16,7 +16,7 @@ ;; The connection test should pass with valid settings (deftest connection-test - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "anonymous binds" (testing "successfully connect to IPv4 host" (is (= {:status :SUCCESS} @@ -70,7 +70,7 @@ (deftest find-test ;; there are EE-specific versions of this test in [[metabase-enterprise.enhancements.integrations.ldap-test]] (mt/with-premium-features #{} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "find by username" (is (= {:dn "cn=John Smith,ou=People,dc=metabase,dc=com" :first-name "John" @@ -104,7 +104,7 @@ (ldap/find-user "jane.miller@metabase.com"))))) ;; Test group lookups for directory servers that use the memberOf attribute overlay, such as Active Directory - (ldap.test/with-active-directory-ldap-server + (ldap.test/with-active-directory-ldap-server! (testing "find user with one group using memberOf attribute" (is (= {:dn "cn=John Smith,ou=People,dc=metabase,dc=com" :first-name "John" @@ -125,7 +125,7 @@ (deftest fetch-or-create-user-test ;; there are EE-specific versions of this test in [[metabase-enterprise.enhancements.integrations.ldap-test]] (mt/with-premium-features #{} - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (testing "a new user is created when they don't already exist" (try (ldap/fetch-or-create-user! (ldap/find-user "jsmith1")) @@ -182,7 +182,7 @@ ;; This isn't a failure of the code, it's a failure of the host. (deftest ipv6-test (testing "successfully connect to IPv6 host" - (let [actual (ldap.test/with-ldap-server + (let [actual (ldap.test/with-ldap-server! (ldap/test-ldap-connection (assoc (ldap.test/get-ldap-details) :host "[::1]")))] (if (= (:status actual) :ERROR) diff --git a/test/metabase/integrations/slack_test.clj b/test/metabase/integrations/slack_test.clj index f812e882488d18f6a4d2a3c2646072385e87fa7d..16e406cdd2b5785dda66bbd365065926ff8f7efc 100644 --- a/test/metabase/integrations/slack_test.clj +++ b/test/metabase/integrations/slack_test.clj @@ -43,7 +43,7 @@ body (json/generate-string body))}) -(defn- test-no-auth-token +(defn- test-no-auth-token! "Test that a Slack API endpoint function returns `nil` if a Slack API token isn't configured." [endpoint thunk] (http-fake/with-fake-routes {endpoint (fn [_] @@ -54,7 +54,7 @@ (is (= nil (not-empty (thunk)))))))) -(defn- test-invalid-auth-token +(defn- test-invalid-auth-token! "Test that a Slack API endpoint function throws an Exception if an invalid Slack API token is set." [endpoint thunk] (testing "should throw Exception if auth token is invalid" @@ -72,10 +72,10 @@ (is (= {:slack-token "Invalid token"} (:errors (ex-data e)))))))))) -(defn- test-auth +(defn- test-auth! "Test that a Slack API `endpoint` function works as expected when Slack token is missing or invalid." [endpoint thunk] - (doseq [f [test-no-auth-token test-invalid-auth-token]] + (doseq [f [test-no-auth-token! test-invalid-auth-token!]] (f endpoint thunk))) (deftest slack-app-token-truncation-test @@ -86,7 +86,7 @@ (deftest conversations-list-test (testing "conversations-list" - (test-auth conversations-endpoint slack/conversations-list) + (test-auth! conversations-endpoint slack/conversations-list) (testing "should be able to fetch channels and paginate" (http-fake/with-fake-routes {conversations-endpoint (comp mock-200-response mock-conversations-response-body)} @@ -138,7 +138,7 @@ (deftest users-list-test (testing "users-list" - (test-auth users-endpoint slack/users-list) + (test-auth! users-endpoint slack/users-list) (testing "should be able to fetch list of users and page" (http-fake/with-fake-routes {users-endpoint (comp mock-200-response mock-users-response-body)} diff --git a/test/metabase/lib/drill_thru/fk_details_test.cljc b/test/metabase/lib/drill_thru/fk_details_test.cljc index c3276568fd63795c395af343da8ff54bc9eb34a9..061c83f715735eff8d54ececaf61ddb830b466b2 100644 --- a/test/metabase/lib/drill_thru/fk_details_test.cljc +++ b/test/metabase/lib/drill_thru/fk_details_test.cljc @@ -144,6 +144,7 @@ (is (not (m/find-first #(= (:type %) :drill-thru/fk-details) (lib/available-drill-thrus query -1 context))))))) +`preserve-filters-for-other-fks-forming-multi-column-pk-test (deftest ^:parallel preserve-filters-for-other-fks-forming-multi-column-pk-test (testing "with multiple FKs forming a multi-column PK on another table" (let [provider (merged-mock/merged-mock-metadata-provider diff --git a/test/metabase/models/collection_test.clj b/test/metabase/models/collection_test.clj index 369c29383fad1149f8319c67f402ad121689cb53..86b541a0a3e12e75a8c7926ba4c010458af03ef3 100644 --- a/test/metabase/models/collection_test.clj +++ b/test/metabase/models/collection_test.clj @@ -144,7 +144,7 @@ ;;; | Nested Collections Helper Fns & Macros | ;;; +----------------------------------------------------------------------------------------------------------------+ -(defn do-with-collection-hierarchy [options a-fn] +(defn do-with-collection-hierarchy! [options a-fn] (mt/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection a (merge options {:name "A"}) Collection b (merge options {:name "B", :location (collection/location-path a)}) @@ -155,7 +155,7 @@ Collection g (merge options {:name "G", :location (collection/location-path a c f)})] (a-fn {:a a, :b b, :c c, :d d, :e e, :f f, :g g})))) -(defmacro with-collection-hierarchy +(defmacro with-collection-hierarchy! "Run `body` with a hierarchy of Collections that looks like: +-> B @@ -170,7 +170,7 @@ ...)" {:style/indent 1} [[collections-binding options] & body] - `(do-with-collection-hierarchy ~options (fn [~collections-binding] ~@body))) + `(do-with-collection-hierarchy! ~options (fn [~collections-binding] ~@body))) (defmacro with-current-user-perms-for-collections "Run `body` with the current User permissions for `collections-or-ids`. @@ -495,7 +495,7 @@ ;; x -+-> C -+-> D -> E ===> x ;; | ;; +-> F -> G - (with-collection-hierarchy [{:keys [a b c d e f g]}] + (with-collection-hierarchy! [{:keys [a b c d e f g]}] (is (= 1 (t2/delete! Collection :id (u/the-id a)))) (is (= 0 @@ -509,7 +509,7 @@ ;; A -+-> x -+-> D -> E ===> A -+ ;; | ;; +-> F -> G - (with-collection-hierarchy [{:keys [a b c d e f g]}] + (with-collection-hierarchy! [{:keys [a b c d e f g]}] (t2/delete! Collection :id (u/the-id c)) (is (= 2 (t2/count Collection :id [:in (map u/the-id [a b c d e f g])])))))) @@ -525,7 +525,7 @@ (map :name (:effective_ancestors (t2/hydrate collection :effective_ancestors)))) (deftest effective-ancestors-test - (with-collection-hierarchy [{:keys [a c d]}] + (with-collection-hierarchy! [{:keys [a c d]}] (testing "For D: if we don't have permissions for C, we should only see A" (with-current-user-perms-for-collections [a d] (is (= ["A"] @@ -573,7 +573,7 @@ format-collections)) (deftest descendants-test - (with-collection-hierarchy [{:keys [a b c d]}] + (with-collection-hierarchy! [{:keys [a b c d]}] (testing "make sure we can fetch the descendants of a Collection in the hierarchy we'd expect" (is (= #{{:name "B", :id true, :location "/A/", :children #{}, :description nil} {:name "C" @@ -615,7 +615,7 @@ (deftest root-collection-descendants-test (testing "For the *Root* Collection, can we get top-level Collections?" - (with-collection-hierarchy [_] + (with-collection-hierarchy! [_] (is (contains? (descendants collection/root-collection) {:name "A" :id true @@ -666,7 +666,7 @@ (set (map :name (collection/effective-children collection)))) (deftest effective-children-test - (with-collection-hierarchy [{:keys [a b c d e f g]}] + (with-collection-hierarchy! [{:keys [a b c d e f g]}] (testing "If we *have* perms for everything we should just see B and C." (with-current-user-perms-for-collections [a b c d e f g] (is (= #{"B" "C"} @@ -739,7 +739,7 @@ (effective-children collection/root-collection))))) (testing "For the Root Collection: if we remove A and C we should get B, D and F" - (with-collection-hierarchy [{:keys [b d e f g]}] + (with-collection-hierarchy! [{:keys [b d e f g]}] (is (= #{"B" "D" "F"} (with-current-user-perms-for-collections [b d e f g] (effective-children collection/root-collection)))))))) @@ -776,7 +776,7 @@ ;;; ---------------------------------------------- Perms for Archiving ----------------------------------------------- (deftest perms-for-archiving-test - (with-collection-hierarchy [{:keys [a b c d], :as collections}] + (with-collection-hierarchy! [{:keys [a b c d], :as collections}] (testing "To Archive A, you should need *write* perms for A and all of its descendants, and also the Root Collection..." (is (= #{"/collection/root/" "/collection/A/" @@ -847,7 +847,7 @@ ;; `*` marks the things that require permissions in charts below! (deftest perms-for-moving-test - (with-collection-hierarchy [{:keys [b c], :as collections}] + (with-collection-hierarchy! [{:keys [b c], :as collections}] (testing "If we want to move B into C, we should need perms for A, B, and C." ;; B because it is being moved; C we are moving ;; something into it, A because we are moving something out of it @@ -912,7 +912,7 @@ (perms-path-ids->names collections))))))) (deftest perms-for-moving-exceptions-test - (with-collection-hierarchy [{:keys [a b], :as _collections}] + (with-collection-hierarchy! [{:keys [a b], :as _collections}] (testing "If you try to calculate permissions to move or archive the Root Collection, throw an Exception!" (is (thrown? Exception @@ -980,7 +980,7 @@ ;; A -+-> C -+-> D -> E ;; | ;; +-> F -> G - (with-collection-hierarchy [collections] + (with-collection-hierarchy! [collections] (is (= {"A" {"B" {} "C" {"D" {"E" {}} "F" {"G" {}}}}} @@ -993,7 +993,7 @@ ;; A -+-> C -+-> D -> E ===> A -+-> C -+-> D ;; | | ;; +-> F -> G +-> F -> G - (with-collection-hierarchy [{:keys [b e], :as collections}] + (with-collection-hierarchy! [{:keys [b e], :as collections}] (collection/move-collection! e (collection/children-location b)) (is (= {"A" {"B" {"E" {}} "C" {"D" {} @@ -1007,7 +1007,7 @@ ;; A -+-> C -+-> D -> E ===> A -+-> C -+ ;; | | ;; +-> F -> G +-> F -> G - (with-collection-hierarchy [{:keys [b d], :as collections}] + (with-collection-hierarchy! [{:keys [b d], :as collections}] (collection/move-collection! d (collection/children-location b)) (is (= {"A" {"B" {"D" {"E" {}}} "C" {"F" {"G" {}}}}} @@ -1020,7 +1020,7 @@ ;; A -+-> C -+-> D -> E ===> A -+-> C -> D -> E ;; | ;; +-> F -> G F -> G - (with-collection-hierarchy [{:keys [f], :as collections}] + (with-collection-hierarchy! [{:keys [f], :as collections}] (collection/move-collection! f (collection/children-location collection/root-collection)) (is (= {"A" {"B" {} "C" {"D" {"E" {}}}} @@ -1034,7 +1034,7 @@ ;; A -+-> C -+-> D -> E ===> F -+-> A -+-> C -+-> D -> E ;; | | ;; +-> F -> G +-> G - (with-collection-hierarchy [{:keys [a f], :as collections}] + (with-collection-hierarchy! [{:keys [a f], :as collections}] (collection/move-collection! f (collection/children-location collection/root-collection)) (collection/move-collection! a (collection/children-location (t2/select-one Collection :id (u/the-id f)))) (is (= {"F" {"A" {"B" {} @@ -1049,7 +1049,7 @@ (deftest nested-collections-archiving-test (testing "Make sure the 'additional-conditions' for collection-locations is working normally" - (with-collection-hierarchy [collections] + (with-collection-hierarchy! [collections] (is (= {"A" {"B" {} "C" {"D" {"E" {}} "F" {"G" {}}}}} @@ -1061,7 +1061,7 @@ ;; A -+-> C -+-> D -> E ===> A -+-> C -+-> D ;; | | ;; +-> F -> G +-> F -> G - (with-collection-hierarchy [{:keys [e], :as collections}] + (with-collection-hierarchy! [{:keys [e], :as collections}] (t2/update! Collection (u/the-id e) {:archived true}) (is (= {"A" {"B" {} "C" {"D" {} @@ -1074,7 +1074,7 @@ ;; A -+-> C -+-> D -> E ===> A -+ ;; | ;; +-> F -> G - (with-collection-hierarchy [{:keys [c], :as collections}] + (with-collection-hierarchy! [{:keys [c], :as collections}] (archive-collection! c) (is (= {"A" {"B" {}}} (collection-locations (vals collections) :archived false)))))) @@ -1086,7 +1086,7 @@ ;; A -+-> C -+-> D ===> A -+-> C -+-> D -> E ;; | | ;; +-> F -> G +-> F -> G - (with-collection-hierarchy [{:keys [e], :as collections}] + (with-collection-hierarchy! [{:keys [e], :as collections}] (archive-collection! e) (unarchive-collection! (t2/select-one :model/Collection :id (u/the-id e))) (is (= {"A" {"B" {} @@ -1100,7 +1100,7 @@ ;; A -+ ===> A -+-> C -+-> D -> E ;; | ;; +-> F -> G - (with-collection-hierarchy [{:keys [c], :as collections}] + (with-collection-hierarchy! [{:keys [c], :as collections}] (archive-collection! c) (unarchive-collection! (t2/select-one :model/Collection :id (u/the-id c))) (is (= {"A" {"B" {} @@ -1112,7 +1112,7 @@ (doseq [model [Card Dashboard NativeQuerySnippet Pulse]] (testing (format "Test that archiving applies to %ss" (name model)) ;; object is in E; archiving E should cause object to be archived - (with-collection-hierarchy [{:keys [e], :as _collections} (when (= model NativeQuerySnippet) + (with-collection-hierarchy! [{:keys [e], :as _collections} (when (= model NativeQuerySnippet) {:namespace "snippets"})] (t2.with-temp/with-temp [model object {:collection_id (u/the-id e)}] (archive-collection! e) @@ -1121,7 +1121,7 @@ (testing (format "Test that archiving applies to %ss belonging to descendant Collections" (name model)) ;; object is in E, a descendant of C; archiving C should cause object to be archived - (with-collection-hierarchy [{:keys [c e], :as _collections} (when (= model NativeQuerySnippet) + (with-collection-hierarchy! [{:keys [c e], :as _collections} (when (= model NativeQuerySnippet) {:namespace "snippets"})] (t2.with-temp/with-temp [model object {:collection_id (u/the-id e)}] (archive-collection! c) @@ -1132,7 +1132,7 @@ (doseq [model [Card Dashboard NativeQuerySnippet Pulse]] (testing (format "Test that unarchiving applies to %ss" (name model)) ;; object is in E; unarchiving E should cause object to be unarchived - (with-collection-hierarchy [{:keys [e], :as _collections} (when (= model NativeQuerySnippet) + (with-collection-hierarchy! [{:keys [e], :as _collections} (when (= model NativeQuerySnippet) {:namespace "snippets"})] (archive-collection! e) (t2.with-temp/with-temp [model object {:collection_id (u/the-id e), :archived true}] @@ -1142,7 +1142,7 @@ (testing (format "Test that unarchiving applies to %ss belonging to descendant Collections" (name model)) ;; object is in E, a descendant of C; unarchiving C should cause object to be unarchived - (with-collection-hierarchy [{:keys [c e], :as _collections} (when (= model NativeQuerySnippet) + (with-collection-hierarchy! [{:keys [c e], :as _collections} (when (= model NativeQuerySnippet) {:namespace "snippets"})] (archive-collection! c) (t2.with-temp/with-temp [model object {:collection_id (u/the-id e), :archived true}] diff --git a/test/metabase/models/dashboard_test.clj b/test/metabase/models/dashboard_test.clj index 564cfcc01b859d6390576b88cee3d12d0e66e91d..423152c005550c7c3ed6f2dcc2830e60d12d49d3 100644 --- a/test/metabase/models/dashboard_test.clj +++ b/test/metabase/models/dashboard_test.clj @@ -855,7 +855,7 @@ ;;; | Collections Permissions Tests | ;;; +----------------------------------------------------------------------------------------------------------------+ -(defn do-with-dash-in-collection [f] +(defn do-with-dash-in-collection! [f] (tu/with-non-admin-groups-no-root-collection-perms (t2.with-temp/with-temp [Collection collection {} Dashboard dash {:collection_id (u/the-id collection)} @@ -867,16 +867,16 @@ DashboardCard _ {:dashboard_id (u/the-id dash), :card_id (u/the-id card)}] (f db collection dash)))) -(defmacro with-dash-in-collection +(defmacro with-dash-in-collection! "Execute `body` with a Dashboard in a Collection. Dashboard will contain one Card in a Database." {:style/indent :defn} [[db-binding collection-binding dash-binding] & body] - `(do-with-dash-in-collection + `(do-with-dash-in-collection! (fn [~(or db-binding '_) ~(or collection-binding '_) ~(or dash-binding '_)] ~@body))) (deftest perms-test - (with-dash-in-collection [_db collection dash] + (with-dash-in-collection! [_db collection dash] (testing (str "Check that if a Dashboard is in a Collection, someone who would not be able to see it under the old " "artifact-permissions regime will be able to see it if they have permissions for that Collection") (binding [api/*current-user-permissions-set* (atom #{(perms/collection-read-path collection)})] diff --git a/test/metabase/models/database_test.clj b/test/metabase/models/database_test.clj index 8dacb83e5b664da1aa2078ef78c475a26888b4b2..d738350104719ac4788108631d4807d26f07a1b4 100644 --- a/test/metabase/models/database_test.clj +++ b/test/metabase/models/database_test.clj @@ -41,7 +41,7 @@ (deftest tasks-test (testing "Sync tasks should get scheduled for a newly created Database" - (mt/with-temp-scheduler + (mt/with-temp-scheduler! (task/init! ::task.sync-databases/SyncDatabases) (t2.with-temp/with-temp [Database {db-id :id}] (is (=? {:description (format "sync-and-analyze Database %d" db-id) diff --git a/test/metabase/models/humanization_test.clj b/test/metabase/models/humanization_test.clj index 3b274c4f179a2e671a8016f5319b3cce1106d45b..ebc4547133fc3c19e4185067b8796b9900732f20 100644 --- a/test/metabase/models/humanization_test.clj +++ b/test/metabase/models/humanization_test.clj @@ -7,7 +7,7 @@ [toucan2.core :as t2] [toucan2.tools.with-temp :as t2.with-temp])) -(defn- get-humanized-display-name [actual-name strategy] +(defn- get-humanized-display-name! [actual-name strategy] (with-redefs [humanization/humanization-strategy (constantly strategy)] (t2.with-temp/with-temp [Table {table-id :id} {:name actual-name}] (t2/select-one-fn :display_name Table, :id table-id)))) @@ -21,7 +21,7 @@ [strategy expected] strategy->expected] (testing (pr-str (list 'get-humanized-display-name input strategy)) (is (= expected - (get-humanized-display-name input strategy))))))) + (get-humanized-display-name! input strategy))))))) (deftest rehumanize-test (testing "check that existing tables have their :display_names updated appropriately when strategy is changed" diff --git a/test/metabase/models/model_index_test.clj b/test/metabase/models/model_index_test.clj index d33fbe58c34119419d688eb92fe65d60003349b8..fc20a248170ba9b2a65d1c352a4959aa8a5f6fb1 100644 --- a/test/metabase/models/model_index_test.clj +++ b/test/metabase/models/model_index_test.clj @@ -21,7 +21,7 @@ [toucan2.core :as t2] [toucan2.tools.with-temp :as t2.with-temp])) -(defmacro with-scheduler-setup [& body] +(defmacro with-scheduler-setup! [& body] `(let [scheduler# (#'tu/in-memory-scheduler)] ;; need cross thread rebinding from with-redefs not a binding (with-redefs [task/scheduler (constantly scheduler#)] @@ -37,7 +37,7 @@ (finally (qs/shutdown scheduler#)))))) (deftest quick-run-through - (with-scheduler-setup + (with-scheduler-setup! (mt/dataset test-data (let [query (mt/mbql-query products {:filter [:and diff --git a/test/metabase/models/params/chain_filter_test.clj b/test/metabase/models/params/chain_filter_test.clj index e934cd5446c699885471a31e7fa2d53aefac0e6d..4b231edac315501b8855018027b27c7af553a70d 100644 --- a/test/metabase/models/params/chain_filter_test.clj +++ b/test/metabase/models/params/chain_filter_test.clj @@ -596,7 +596,7 @@ (is (#'chain-filter/use-cached-field-values? %myfield))) (thunk))))))))) -(defn- do-with-clean-field-values-for-field +(defn- do-with-clean-field-values-for-field! [field-or-field-id thunk] (mt/with-model-cleanup [FieldValues] (let [field-id (u/the-id field-or-field-id) @@ -612,18 +612,18 @@ (t2/update! Field field-id {:has_field_values has_field_values}) (t2/insert! FieldValues fvs)))))) -(defmacro ^:private with-clean-field-values-for-field +(defmacro ^:private with-clean-field-values-for-field! "Run `body` with all FieldValues for `field-id` deleted. Restores the deleted FieldValues when we're done." {:style/indent 1} [field-or-field-id & body] - `(do-with-clean-field-values-for-field ~field-or-field-id (fn [] ~@body))) + `(do-with-clean-field-values-for-field! ~field-or-field-id (fn [] ~@body))) (deftest chain-filter-has-more-values-test (testing "the `has_more_values` property should be correct\n" (testing "for cached fields" (testing "without contraints" - (with-clean-field-values-for-field (mt/id :categories :name) + (with-clean-field-values-for-field! (mt/id :categories :name) (testing "`false` for field has values less than [[field-values/*total-max-length*]] threshold" (is (= false (:has_more_values (chain-filter categories.name {}))))) @@ -636,13 +636,13 @@ (:has_more_values (chain-filter categories.name {} :limit Integer/MAX_VALUE)))))) (testing "`true` if the values of a field exceeds our [[field-values/*total-max-length*]] limit" - (with-clean-field-values-for-field (mt/id :categories :name) + (with-clean-field-values-for-field! (mt/id :categories :name) (binding [field-values/*total-max-length* 10] (is (= true (:has_more_values (chain-filter categories.name {})))))))) (testing "with contraints" - (with-clean-field-values-for-field (mt/id :categories :name) + (with-clean-field-values-for-field! (mt/id :categories :name) (testing "`false` for field has values less than [[field-values/*total-max-length*]] threshold" (is (= false (:has_more_values (chain-filter categories.name {venues.price 4}))))) @@ -654,7 +654,7 @@ (is (= false (:has_more_values (chain-filter categories.name {venues.price 4} :limit Integer/MAX_VALUE)))))) - (with-clean-field-values-for-field (mt/id :categories :name) + (with-clean-field-values-for-field! (mt/id :categories :name) (testing "`true` if the values of a field exceeds our [[field-values/*total-max-length*]] limit" (binding [field-values/*total-max-length* 10] (is (= true @@ -662,7 +662,7 @@ (testing "for non-cached fields" (testing "with contraints" - (with-clean-field-values-for-field (mt/id :venues :latitude) + (with-clean-field-values-for-field! (mt/id :venues :latitude) (testing "`false` if we don't specify limit" (is (= false (:has_more_values (chain-filter venues.latitude {venues.price 4}))))) diff --git a/test/metabase/models/params_test.clj b/test/metabase/models/params_test.clj index 1259fad991c76b44144e2af5fc073f11a6248d99..5f983b0abf2e1dc798724ecdb0eb2677f46ae641 100644 --- a/test/metabase/models/params_test.clj +++ b/test/metabase/models/params_test.clj @@ -100,7 +100,7 @@ (deftest hydate-param-fields-for-dashboard-test (testing "check that we can hydrate param_fields for a Dashboard" - (public-test/with-sharing-enabled-and-temp-dashcard-referencing :venues :id [dashboard] + (public-test/with-sharing-enabled-and-temp-dashcard-referencing! :venues :id [dashboard] (is (= {(mt/id :venues :id) {:id (mt/id :venues :id) :table_id (mt/id :venues) :display_name "ID" diff --git a/test/metabase/models/pulse_channel_test.clj b/test/metabase/models/pulse_channel_test.clj index 2a345a89997c3aa288d1892168829acfee71f3c3..6fadbdd55efd3730e2804fbeb3a429ab334a9093 100644 --- a/test/metabase/models/pulse_channel_test.clj +++ b/test/metabase/models/pulse_channel_test.clj @@ -431,7 +431,7 @@ (defmacro with-send-pulse-setup! [& body] - `(mt/with-temp-scheduler + `(mt/with-temp-scheduler! (task/init! ::task.send-pulses/SendPulses) ~@body)) diff --git a/test/metabase/models/pulse_test.clj b/test/metabase/models/pulse_test.clj index f1a4a71b95c42508ba6df366d0d540dcc1be9246..9cc57a65d1a2258aa1fddf0c6c1dd31d70018787 100644 --- a/test/metabase/models/pulse_test.clj +++ b/test/metabase/models/pulse_test.clj @@ -434,7 +434,7 @@ ;;; | Pulse Collections Permissions Tests | ;;; +----------------------------------------------------------------------------------------------------------------+ -(defn do-with-pulse-in-collection [f] +(defn do-with-pulse-in-collection! [f] (mt/with-non-admin-groups-no-root-collection-perms (mt/with-temp [Collection collection {} Pulse pulse {:collection_id (u/the-id collection)} @@ -446,11 +446,11 @@ PulseCard _ {:pulse_id (u/the-id pulse) :card_id (u/the-id card)}] (f db collection pulse card)))) -(defmacro with-pulse-in-collection +(defmacro with-pulse-in-collection! "Execute `body` with a temporary Pulse, in a Collection, containing a single Card." {:style/indent :defn} [[db-binding collection-binding pulse-binding card-binding] & body] - `(do-with-pulse-in-collection + `(do-with-pulse-in-collection! (fn [~(or db-binding '_) ~(or collection-binding '_) ~(or pulse-binding '_) ~(or card-binding '_)] ~@body))) @@ -477,7 +477,7 @@ ;;; | Dashboard Subscription Collections Permissions Tests | ;;; +----------------------------------------------------------------------------------------------------------------+ -(defn- do-with-dashboard-subscription-in-collection [f] +(defn- do-with-dashboard-subscription-in-collection! [f] (mt/with-non-admin-groups-no-root-collection-perms (mt/with-temp [Collection collection {} Dashboard dashboard {:collection_id (u/the-id collection)} @@ -487,16 +487,16 @@ Database db {:engine :h2}] (f db collection dashboard pulse)))) -(defmacro with-dashboard-subscription-in-collection +(defmacro with-dashboard-subscription-in-collection! "Execute `body` with a temporary Dashboard Subscription created by :rasta (a non-admin) for a Dashboard in a Collection" {:style/indent 1} [[db-binding collection-binding dashboard-binding subscription-binding] & body] - `(do-with-dashboard-subscription-in-collection + `(do-with-dashboard-subscription-in-collection! (fn [~(or db-binding '_) ~(or collection-binding '_) ~(or dashboard-binding '_) ~(or subscription-binding '_)] ~@body))) (deftest dashboard-subscription-permissions-test - (with-dashboard-subscription-in-collection [_ collection dashboard subscription] + (with-dashboard-subscription-in-collection! [_ collection dashboard subscription] (testing "An admin has read and write access to any dashboard subscription" (binding [api/*is-superuser?* true] (is (mi/can-read? subscription)) diff --git a/test/metabase/models/setting_test.clj b/test/metabase/models/setting_test.clj index 9a080233cc3fb72cdecc97bceab48b35f5f07c89..908957afa5614000cecfee3b50776748ace14d57 100644 --- a/test/metabase/models/setting_test.clj +++ b/test/metabase/models/setting_test.clj @@ -317,8 +317,8 @@ ;; these tests are to check that settings get returned with the correct information; these functions are what feed ;; into the API -(defn- user-facing-info-with-db-and-env-var-values [setting db-value env-var-value] - (tu/do-with-temporary-setting-value setting db-value +(defn- user-facing-info-with-db-and-env-var-values! [setting db-value env-var-value] + (tu/do-with-temporary-setting-value! setting db-value (fn [] (tu/do-with-temp-env-var-value! (setting/setting-env-map-name (keyword setting)) @@ -330,35 +330,35 @@ (deftest user-facing-info-test (testing "user-facing info w/ no db value, no env var value, no default value" (is (= {:value nil, :is_env_setting false, :env_name "MB_TEST_SETTING_1", :default nil} - (user-facing-info-with-db-and-env-var-values :test-setting-1 nil nil)))) + (user-facing-info-with-db-and-env-var-values! :test-setting-1 nil nil)))) (testing "user-facing info w/ no db value, no env var value, default value" (is (= {:value nil, :is_env_setting false, :env_name "MB_TEST_SETTING_2", :default "[Default Value]"} - (user-facing-info-with-db-and-env-var-values :test-setting-2 nil nil)))) + (user-facing-info-with-db-and-env-var-values! :test-setting-2 nil nil)))) (testing "user-facing info w/ no db value, env var value, no default value -- shouldn't leak env var value" (is (= {:value nil, :is_env_setting true, :env_name "MB_TEST_SETTING_1", :default "Using value of env var $MB_TEST_SETTING_1"} - (user-facing-info-with-db-and-env-var-values :test-setting-1 nil "TOUCANS")))) + (user-facing-info-with-db-and-env-var-values! :test-setting-1 nil "TOUCANS")))) (testing "user-facing info w/ no db value, env var value, default value" (is (= {:value nil, :is_env_setting true, :env_name "MB_TEST_SETTING_2", :default "Using value of env var $MB_TEST_SETTING_2"} - (user-facing-info-with-db-and-env-var-values :test-setting-2 nil "TOUCANS")))) + (user-facing-info-with-db-and-env-var-values! :test-setting-2 nil "TOUCANS")))) (testing "user-facing info w/ db value, no env var value, no default value" (is (= {:value "WOW", :is_env_setting false, :env_name "MB_TEST_SETTING_1", :default nil} - (user-facing-info-with-db-and-env-var-values :test-setting-1 "WOW" nil)))) + (user-facing-info-with-db-and-env-var-values! :test-setting-1 "WOW" nil)))) (testing "user-facing info w/ db value, no env var value, default value" (is (= {:value "WOW", :is_env_setting false, :env_name "MB_TEST_SETTING_2", :default "[Default Value]"} - (user-facing-info-with-db-and-env-var-values :test-setting-2 "WOW" nil)))) + (user-facing-info-with-db-and-env-var-values! :test-setting-2 "WOW" nil)))) (testing "user-facing info w/ db value, env var value, no default value -- the env var should take precedence over the db value, but should be obfuscated" (is (= {:value nil, :is_env_setting true, :env_name "MB_TEST_SETTING_1", :default "Using value of env var $MB_TEST_SETTING_1"} - (user-facing-info-with-db-and-env-var-values :test-setting-1 "WOW" "ENV VAR")))) + (user-facing-info-with-db-and-env-var-values! :test-setting-1 "WOW" "ENV VAR")))) (testing "user-facing info w/ db value, env var value, default value -- env var should take precedence over default, but should be obfuscated" (is (= {:value nil, :is_env_setting true, :env_name "MB_TEST_SETTING_2", :default "Using value of env var $MB_TEST_SETTING_2"} - (user-facing-info-with-db-and-env-var-values :test-setting-2 "WOW" "ENV VAR"))))) + (user-facing-info-with-db-and-env-var-values! :test-setting-2 "WOW" "ENV VAR"))))) (deftest writable-settings-test (testing `setting/writable-settings @@ -416,7 +416,7 @@ (deftest validate-description-test (testing "Validate setting description with i18n string" (mt/with-test-user :crowberto - (mt/with-mock-i18n-bundles {"zz" {:messages {"Test setting - with i18n" "TEST SETTING - WITH I18N"}}} + (mt/with-mock-i18n-bundles! {"zz" {:messages {"Test setting - with i18n" "TEST SETTING - WITH I18N"}}} (letfn [(description [] (some (fn [{:keys [key description]}] (when (= :test-i18n-setting key) @@ -434,7 +434,7 @@ (deftest dynamic-description-test (testing "Descriptions with i18n string should update if it depends on another setting's value." (mt/with-test-user :crowberto - (mt/with-mock-i18n-bundles {"zz" {:messages {"Test setting - with i18n: {0}" "TEST SETTING - WITH I18N: {0}"}}} + (mt/with-mock-i18n-bundles! {"zz" {:messages {"Test setting - with i18n: {0}" "TEST SETTING - WITH I18N: {0}"}}} (letfn [(description [] (some (fn [{:keys [key description]}] (when (= :test-dynamic-i18n-setting key) @@ -457,7 +457,7 @@ (deftest boolean-setting-user-facing-info-test (is (= {:value nil, :is_env_setting false, :env_name "MB_TEST_BOOLEAN_SETTING", :default nil} - (user-facing-info-with-db-and-env-var-values :test-boolean-setting nil nil)))) + (user-facing-info-with-db-and-env-var-values! :test-boolean-setting nil nil)))) (deftest boolean-setting-env-vars-test (testing "values set by env vars should never be shown to the User" @@ -466,11 +466,11 @@ :env_name "MB_TEST_BOOLEAN_SETTING" :default "Using value of env var $MB_TEST_BOOLEAN_SETTING"}] (is (= expected - (user-facing-info-with-db-and-env-var-values :test-boolean-setting nil "true"))) + (user-facing-info-with-db-and-env-var-values! :test-boolean-setting nil "true"))) (testing "env var values should be case-insensitive" (is (= expected - (user-facing-info-with-db-and-env-var-values :test-boolean-setting nil "TRUE")))))) + (user-facing-info-with-db-and-env-var-values! :test-boolean-setting nil "TRUE")))))) (testing "if value isn't true / false" (testing "getter should throw exception" @@ -484,7 +484,7 @@ :is_env_setting true :env_name "MB_TEST_BOOLEAN_SETTING" :default "Using value of env var $MB_TEST_BOOLEAN_SETTING"} - (user-facing-info-with-db-and-env-var-values :test-boolean-setting nil "X")))))) + (user-facing-info-with-db-and-env-var-values! :test-boolean-setting nil "X")))))) (deftest set-boolean-setting-test (testing "should be able to set value with a string..." @@ -511,18 +511,18 @@ ;;; -------------------------------------------------- CSV Settings -------------------------------------------------- -(defn- fetch-csv-setting-value [v] +(defn- fetch-csv-setting-value! [v] (with-redefs [setting/db-or-cache-value (constantly v)] (test-csv-setting))) (deftest get-csv-setting-test (testing "should be able to fetch a simple CSV setting" (is (= ["A" "B" "C"] - (fetch-csv-setting-value "A,B,C")))) + (fetch-csv-setting-value! "A,B,C")))) (testing "should also work if there are quoted values that include commas in them" (is (= ["A" "B" "C1,C2" "ddd"] - (fetch-csv-setting-value "A,B,\"C1,C2\",ddd"))))) + (fetch-csv-setting-value! "A,B,\"C1,C2\",ddd"))))) (defn- set-and-fetch-csv-setting-value! [v] (test-csv-setting! v) diff --git a/test/metabase/models/user_test.clj b/test/metabase/models/user_test.clj index ee0faedc14167087c260dc66334e73a325a1a6de..a43833866a40e978dc3fb72a43148e02ff93f5bf 100644 --- a/test/metabase/models/user_test.clj +++ b/test/metabase/models/user_test.clj @@ -195,7 +195,7 @@ (testing "if sso enabled and password login is disabled, email should send a link to sso login" (mt/with-premium-features #{:disable-password-login} (mt/with-temporary-setting-values [enable-password-login false] - (ldap.test/with-ldap-server + (ldap.test/with-ldap-server! (invite-user-accept-and-check-inboxes! :invitor default-invitor , :accept-invite? false) (is (seq (mt/regex-email-bodies #"/auth/login")))))))) diff --git a/test/metabase/public_settings_test.clj b/test/metabase/public_settings_test.clj index 1d819f140f480aa7373381d2d2e62c14b9bd8e41..ea8a55be7acccc5e519fcddcb4ef93145f0edb6c 100644 --- a/test/metabase/public_settings_test.clj +++ b/test/metabase/public_settings_test.clj @@ -96,14 +96,14 @@ (public-settings/redirect-all-requests-to-https)))))) (deftest translate-public-setting - (mt/with-mock-i18n-bundles {"zz" {:messages {"Host" "HOST"}}} + (mt/with-mock-i18n-bundles! {"zz" {:messages {"Host" "HOST"}}} (mt/with-user-locale "zz" (is (= "HOST" (str (get-in (setting/user-readable-values-map #{:public}) [:engines :postgres :details-fields 0 :display-name]))))))) (deftest tru-translates - (mt/with-mock-i18n-bundles {"zz" {:messages {"Host" "HOST"}}} + (mt/with-mock-i18n-bundles! {"zz" {:messages {"Host" "HOST"}}} (mt/with-user-locale "zz" (is (= true (= (i18n/locale "zz") diff --git a/test/metabase/pulse/pulse_integration_test.clj b/test/metabase/pulse/pulse_integration_test.clj index 6bea4d617696b81e140b845eab0b76339ae2e365..be7c80b13e1b0009bbae22b56f6e9681f8fa4285 100644 --- a/test/metabase/pulse/pulse_integration_test.clj +++ b/test/metabase/pulse/pulse_integration_test.clj @@ -595,7 +595,7 @@ (is (= ["IDENTIFIER" "Tax" "Grand Total" "Amount of Discount ($)" "Count" "Tax Rate"] (attachment-name->cols (format "%s.csv" question-card-name))))))))))) -(defn- run-pulse-and-return-scalars +(defn- run-pulse-and-return-scalars! "Simulate sending the pulse email, get the html body of the response and return the scalar value of the card." [pulse] (mt/with-fake-inbox @@ -654,9 +654,9 @@ ;; First value is the scalar returned from card1 (specified "TAX" field directly in the query) ;; Second value is the scalar returned from card2 (scalar field specified only in viz-settings, not the query) (is (= ["2.07" "2.07"] - (run-pulse-and-return-scalars pulse)))))))) + (run-pulse-and-return-scalars! pulse)))))))) -(defn- run-pulse-and-return-data-tables +(defn- run-pulse-and-return-data-tables! "Run the pulse and return the sequence of inlined html tables as data. Empty tables will be []. If not pulse is sent, return `nil`." [pulse] @@ -679,7 +679,7 @@ (mapv (comp (partial mapv (comp first :content)) :content)))) data-tables))))) -(defmacro with-skip-if-empty-pulse-result +(defmacro ^:private with-skip-if-empty-pulse-result! "Provide a fixture that runs body using the provided pulse results (symbol), the value of `:skip_if_empty` for the pulse, and the queries for two cards. This enables a variety of cases to test the behavior of `:skip_if_empty` based on the presence or absence of card data." @@ -715,7 +715,7 @@ :enabled true} PulseChannelRecipient ~'_ {:pulse_channel_id ~'pulse-channel-id :user_id (mt/user->id :rasta)}] - (let [~result (run-pulse-and-return-data-tables ~'pulse)] + (let [~result (run-pulse-and-return-data-tables! ~'pulse)] ~@body))) (deftest skip-if-empty-test @@ -732,23 +732,23 @@ (testing "Cases for when 'Don't send if there aren't results is enabled' is false" (let [skip-if-empty? false] (testing "Everything has results" - (with-skip-if-empty-pulse-result [result skip-if-empty? query query2] + (with-skip-if-empty-pulse-result! [result skip-if-empty? query query2] (testing "Show all the data" (is (= [[["1" "2.07"] ["2" "6.1"]] [["1" "2.07"] ["2" "6.1"] ["3" "2.9"]]] result))))) (testing "Not everything has results" - (with-skip-if-empty-pulse-result [result skip-if-empty? query empty-query] + (with-skip-if-empty-pulse-result! [result skip-if-empty? query empty-query] (testing "The second table is empty since there are no results" (is (= [[["1" "2.07"] ["2" "6.1"]] []] result))))) (testing "No results" - (with-skip-if-empty-pulse-result [result skip-if-empty? empty-query empty-query] + (with-skip-if-empty-pulse-result! [result skip-if-empty? empty-query empty-query] (testing "We send the email anyways, despite everything being empty due to no results" (is (= [[] []] result))))))) (testing "Cases for when 'Don't send if there aren't results is enabled' is true" (let [skip-if-empty? true] (testing "Everything has results" - (with-skip-if-empty-pulse-result [result skip-if-empty? query query2] + (with-skip-if-empty-pulse-result! [result skip-if-empty? query query2] (testing "When everything has results, we see everything" (is (= 2 (count result)))) (testing "Show all the data" @@ -756,13 +756,13 @@ [["1" "2.07"] ["2" "6.1"] ["3" "2.9"]]] result))))) (testing "Not everything has results" - (with-skip-if-empty-pulse-result [result skip-if-empty? query empty-query] + (with-skip-if-empty-pulse-result! [result skip-if-empty? query empty-query] (testing "We should only see a single data table in the result" (is (= 1 (count result)))) (testing "The single result should contain the card with data in it" (is (= [[["1" "2.07"] ["2" "6.1"]]] result))))) (testing "No results" - (with-skip-if-empty-pulse-result [result skip-if-empty? empty-query empty-query] + (with-skip-if-empty-pulse-result! [result skip-if-empty? empty-query empty-query] (testing "Don't send a pulse if no results at all" (is (nil? result))))))))))) @@ -907,7 +907,7 @@ ["3" "2.19777989° W" "57.20190048° N"] ["4" "89.67790222° W" "39.84410095° N"] ["5" "54.65110016° E" "24.43300056° N"]]] - (run-pulse-and-return-data-tables pulse))))))))) + (run-pulse-and-return-data-tables! pulse))))))))) (deftest empty-dashboard-test (testing "A completely empty dashboard should still send an email" diff --git a/test/metabase/pulse/render/body_test.clj b/test/metabase/pulse/render/body_test.clj index 55f6f6d95d60e1f31bc796c2f02738013dfcc594..feab51385f70fff4a3c1ad467c710e71d7c6650d 100644 --- a/test/metabase/pulse/render/body_test.clj +++ b/test/metabase/pulse/render/body_test.clj @@ -480,7 +480,7 @@ {:key "January", :name "January", :enabled true}], :funnel.order_dimension "CREATED_AT"}}] (mt/with-temp [Card {card-id :id} funnel-card] - (let [doc (render.tu/render-card-as-hickory card-id) + (let [doc (render.tu/render-card-as-hickory! card-id) pulse-body (hik.s/select (hik.s/class "pulse-body") doc)] @@ -522,7 +522,7 @@ {:key "purchase" :name "purchase" :enabled true}]}}] (mt/with-temp [:model/Card {card-id :id} funnel-card] (let [row-names (into #{} (map first funnel-rows)) - doc (render.tu/render-card-as-hickory card-id) + doc (render.tu/render-card-as-hickory! card-id) section-labels (->> doc (hik.s/select (hik.s/tag :tspan)) (mapv (comp first :content)) @@ -553,8 +553,8 @@ :pie.show_total false :pie.colors colours} :dataset_query q}] - (let [card-a-doc (render.tu/render-card-as-hickory card-a-id) - card-b-doc (render.tu/render-card-as-hickory card-b-id)] + (let [card-a-doc (render.tu/render-card-as-hickory! card-a-id) + card-b-doc (render.tu/render-card-as-hickory! card-b-id)] ;; The test asserts that all 4 slices exist by seeing that each path element has the colour assigned to that category ;; we should expect to see each of the 4 (and only those 4) colours. ;; This is also true of the colours for the legend circle elements. @@ -665,7 +665,7 @@ :dataset_query q :visualization_settings viz}] (testing "the render succeeds with unknown column settings keys" - (is (seq (render.tu/render-card-as-hickory card-id)))))))))) + (is (seq (render.tu/render-card-as-hickory! card-id)))))))))) (deftest trend-chart-renders-in-alerts-test (testing "Trend charts render successfully in Alerts. (#39854)" @@ -682,7 +682,7 @@ ;; Here, we simulate an Alert (NOT a subscription) by only providing a card and not mocking a DashCard. (mt/with-temp [:model/Card {card-id :id} {:display :smartscalar :dataset_query q}] - (let [doc (render.tu/render-card-as-hickory card-id) + (let [doc (render.tu/render-card-as-hickory! card-id) span-text (->> doc (hik.s/select (hik.s/tag :span)) (mapv (comp first :content)) @@ -731,7 +731,7 @@ :visualization_settings (viz "right") :dataset_query q}] (testing "Every series on the left correctly only renders left axis." - (let [doc (render.tu/render-card-as-hickory left-card-id) + (let [doc (render.tu/render-card-as-hickory! left-card-id) axis-label-element (hik.s/select (content-selector ["Count"]) doc) ;; the axis label has a :transform property like this: "matrix(0,1,-1,0,520,162.3245)" ;; which is explained here: https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform @@ -747,7 +747,7 @@ (is (= 1 (count axis-label-element))) (is (> 200 axis-y-transform)))) (testing "Every series on the right correctly only renders right axis." - (let [doc (render.tu/render-card-as-hickory right-card-id) + (let [doc (render.tu/render-card-as-hickory! right-card-id) axis-label-element (hik.s/select (content-selector ["Count"]) doc) axis-y-transform (-> axis-label-element (get-in [0 :attrs :transform]) @@ -776,8 +776,8 @@ :model/DashboardCardSeries _ {:dashboardcard_id dashcard-id :card_id card-b-id}] (mt/with-current-user (mt/user->id :rasta) - (let [card-doc (render.tu/render-card-as-hickory card-a-id) - dashcard-doc (render.tu/render-dashcard-as-hickory dashcard-id) + (let [card-doc (render.tu/render-card-as-hickory! card-a-id) + dashcard-doc (render.tu/render-dashcard-as-hickory! dashcard-id) card-path-elements (hik.s/select (hik.s/tag :path) card-doc) card-paths-count (count card-path-elements) dashcard-path-elements (hik.s/select (hik.s/tag :path) dashcard-doc) @@ -827,7 +827,7 @@ :model/DashboardCardSeries _ {:dashboardcard_id dashcard-id :card_id card-b-id}] (mt/with-current-user (mt/user->id :rasta) - (let [dashcard-doc (render.tu/render-dashcard-as-hickory + (let [dashcard-doc (render.tu/render-dashcard-as-hickory! dashcard-id [{:value "2019-05" :id "944bba5f" @@ -885,9 +885,9 @@ :column_settings {(format "[\"ref\",[\"field\",%d,null]]" (mt/id :orders :total)) {:column_title "CASH MONEY"}}}}] (mt/with-current-user (mt/user->id :rasta) - (let [card-doc (render.tu/render-card-as-hickory card-id) + (let [card-doc (render.tu/render-card-as-hickory! card-id) card-header-els (hik.s/select (hik.s/tag :th) card-doc) - dashcard-doc (render.tu/render-dashcard-as-hickory dashcard-id) + dashcard-doc (render.tu/render-dashcard-as-hickory! dashcard-id) dash-header-els (hik.s/select (hik.s/tag :th) dashcard-doc) card-header ["ID" "User ID" "Product ID" "SUB CASH MONEY" "Tax" "Total" "Discount ($)" "Created At" "Quantity"] @@ -920,7 +920,7 @@ :id idx}) ids-to-colour))}}] (mt/with-current-user (mt/user->id :rasta) - (let [card-doc (render.tu/render-card-as-hickory card-id) + (let [card-doc (render.tu/render-card-as-hickory! card-id) card-row-els (hik.s/select (hik.s/tag :tr) card-doc)] (is (= (mapv str ids-to-colour) (keep @@ -962,7 +962,7 @@ :id idx}) ids-to-colour))}}] (mt/with-current-user (mt/user->id :rasta) - (let [card-doc (render.tu/render-card-as-hickory card-id) + (let [card-doc (render.tu/render-card-as-hickory! card-id) card-row-els (hik.s/select (hik.s/tag :tr) card-doc)] (is (= ids-to-colour (keep diff --git a/test/metabase/pulse/render/color_test.clj b/test/metabase/pulse/render/color_test.clj index d5a9d617ef2862f64f19fc941d16d2f89d522a60..4f686d6a1f777e911841625eb6e534f213d9d70f 100644 --- a/test/metabase/pulse/render/color_test.clj +++ b/test/metabase/pulse/render/color_test.clj @@ -22,7 +22,7 @@ } }") -(defmacro ^:private with-test-js-engine +(defmacro ^:private with-test-js-engine! "Setup a javascript engine with a stubbed script useful making sure `get-background-color` works independently from the real color picking script" [script & body] @@ -33,7 +33,7 @@ (deftest color-test (testing "The test script above should return red on even rows, green on odd rows" - (with-test-js-engine test-script + (with-test-js-engine! test-script (let [color-selector (color/make-color-selector {:cols [{:name "test"}] :rows [[1] [2] [3] [4]]} {"even" red, "odd" green})] @@ -44,7 +44,7 @@ (deftest convert-keywords-test (testing (str "Same test as above, but make sure we convert any keywords as keywords don't get converted to " "strings automatically when passed to a JavaScript function") - (with-test-js-engine test-script + (with-test-js-engine! test-script (let [color-selector (color/make-color-selector {:cols [{:name "test"}] :rows [[1] [2] [3] [4]]} {:even red, :odd green})] diff --git a/test/metabase/pulse/render/table_test.clj b/test/metabase/pulse/render/table_test.clj index be6609df2d725ec88a3c69844aa1cbec2d494c98..ef57cc335910d50012bade8e7363976c4e303b3f 100644 --- a/test/metabase/pulse/render/table_test.clj +++ b/test/metabase/pulse/render/table_test.clj @@ -129,7 +129,7 @@ :native {:query q}} :visualization_settings formatting-viz}] (testing "Custom column titles and column format settings are respected in render." - (let [doc (render.tu/render-card-as-hickory card-id) + (let [doc (render.tu/render-card-as-hickory! card-id) row-els (hik.s/select (hik.s/tag :tr) doc)] (is (= [["Eh" "Bee" "Sea" "D" "E"] ["10%" "9E3" "12/10/2022" "---0.12___" "0.667"]] @@ -139,7 +139,7 @@ :date_separator "-" :date_abbreviate false} :type/Number {:number_separators ",."}}] - (let [doc (render.tu/render-card-as-hickory card-id) + (let [doc (render.tu/render-card-as-hickory! card-id) row-els (hik.s/select (hik.s/tag :tr) doc)] (is (= [["Eh" "Bee" "Sea" "D" "E"] ["10%" "9E3" "12-10-2022" "---0,12___" "0,667"]] @@ -165,7 +165,7 @@ :type :native :native {:query q}} :visualization_settings disabled-cols-viz}] - (let [doc (render.tu/render-card-as-hickory card-id) + (let [doc (render.tu/render-card-as-hickory! card-id) row-els (hik.s/select (hik.s/tag :tr) doc)] (is (= [["B" "A"] ["9,000" "0.1"]] diff --git a/test/metabase/pulse/render/test_util.clj b/test/metabase/pulse/render/test_util.clj index 35906cb5c0bb8800065ee9daac57bc7bf1472b8b..f67fd31dfaf50770704ffa9179ae70f01ba15696 100644 --- a/test/metabase/pulse/render/test_util.clj +++ b/test/metabase/pulse/render/test_util.clj @@ -10,7 +10,6 @@ [hiccup.core :as hiccup] [hickory.core :as hik] [metabase.pulse.render :as render] - [metabase.pulse.render.body :as body] [metabase.pulse.render.image-bundle :as image-bundle] [metabase.pulse.render.js-svg :as js-svg] [metabase.pulse.util :as pu] @@ -43,10 +42,6 @@ :max_value 9 :colors ["#00ff00" "#0000ff"]}]}}) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; render-as-hiccup -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - (defn- node-attrs->map [^AbstractElement$ExtendedNamedNodeHashMap attrs] (when attrs @@ -81,10 +76,10 @@ (defn- edit-nodes "Returns a tree of nodes where any node that matches the `matcher` predicate is modified by the `edit-fn`. - A `matcher` function takes a zipper location `loc`, and should return `true` or `false`. - An `edit-fn` function takes a location `loc`, and returns a `loc`, which is easiest to modify with `zip/replace` or `zip/edit`. + A `matcher` function takes a zipper location `loc`, and should return `true` or `false`. An `edit-fn` function takes + a location `loc`, and returns a `loc`, which is easiest to modify with `zip/replace` or `zip/edit`. - See the `render-as-hiccup` function for an example usage of this function." + See the [[render-card-as-hickory!]] function for an example usage of this function." [tree matcher edit-fn] (loop [loc (hiccup-zip tree)] (if (zip/end? loc) @@ -100,90 +95,18 @@ (= tag :img) (str/starts-with? src "<svg")))) -(defn- wrapped-children? - [loc] - (let [children (remove #(or (map? %) (string? %) (nil? %)) (rest (zip/node loc)))] - (and (every? seq? children) - (seq children)))) - -(defn- wrapped-node? - [loc] - (let [node (zip/node loc)] - (and (= 1 (count node)) - (seqable? node)))) - (def ^:private parse-svg #'js-svg/parse-svg-string) (defn- img-node->svg-node - "Modifies an intentionally malformed [:img {:src \"<svg>...</svg>\"}] node by parsing the svg string and - replacing the entire :img node with the `svg-content` hiccup tree. The malformed node is a result of the - `render-as-hiccup` function which redefines some functionality of the static-viz rendering pipeline. See - `render-as-hiccup` in this namespace for details." + "Modifies an intentionally malformed [:img {:src \"<svg>...</svg>\"}] node by parsing the svg string and replacing the + entire :img node with the `svg-content` hiccup tree. The malformed node is a result of the + [[render-card-as-hickory!]] function which redefines some functionality of the static-viz rendering pipeline. See + [[render-card-as-hickory!]] in this namespace for details." [loc] (let [[_ attrs] (zip/node loc) svg-content (-> attrs :src parse-svg document-tag-hiccup)] (zip/replace loc svg-content))) -(defn- unwrap-children - [loc] - (let [node (zip/node loc) - k (first node) - attrs (if (map? (second node)) (second node) {}) - children (remove #(or (map? %) (nil? %)) (rest (zip/node loc)))] - (zip/replace loc (into [k attrs] (first children))))) - -(defn- unwrap-node - [loc] - (zip/replace loc (first (zip/node loc)))) - -(defn render-as-hiccup - "Renders a card-and-data map using the static-viz rendering pipeline, returning a hiccup tree from the html/SVG. - The input map requires: - `:card` which contains a map with the necessary keys to configure a visualization. - `:data` which is map that mimics the shape and settings returned by executing a card's :dataset_query with - [[metabase.query-processor/process-query]] + [[metabase.query-processor/userland-query]], and - the :process-viz-settings? middleware. For example: - - ``` - (let [card-id 1 - {:keys [dataset_query] :as card} (t2/select-one card/Card :id card-id) - user (t2/select-one user/User) - query-results (binding [qp.perms/*card-id* nil] - (qp/process-query - (qp/userland-query - (-> dataset_query - (assoc-in [:middleware :process-viz-settings?] true)) - {:executed-by (:id user) - :context :pulse - :card-id card-id})))] - {:data query-results}) - ``` - - The intent of these test utils, however, is to avoid the need to run the query processor like this, and just - work with the data directly. - - Rendering the result as a hiccup tree is acheived by redefining 2 functions: - - [[metabase.pulse.render.js-svg/svg-string->bytes]] normally takes an svg-string from the static-viz js (via gaalvm) - and returns PNG bytes. It is redefined to pass the svg-string without any encoding. - - [[metabase.pulse.render.image-bundle/make-image-bundle]] normally takes a render-type (:inline :attachment) and - image-bytes, and returns a map containing the image as a base64 encoded string, suitable for an inline src string - to embed the PNG in an html img tag. It is redefined to pass the string unmodified. - - This does result in a malformed img tag, because the src string ends up being an svg-string, but we immediately - extract and replace this tag with the `img-node->svg-node` function." - [{:keys [card data]}] - (with-redefs [js-svg/svg-string->bytes identity - image-bundle/make-image-bundle (fn [_ s] - {:image-src s - :render-type :inline})] - (let [content (-> (body/render (render/detect-pulse-chart-type card nil data) :inline "UTC" card nil data) - :content)] - (-> content - (edit-nodes img-node-with-svg? img-node->svg-node) ;; replace the :img tag with its parsed SVG. - (edit-nodes wrapped-node? unwrap-node) ;; eg: ([:div "content"]) -> [:div "content"] - (edit-nodes wrapped-children? unwrap-children))))) ;; eg: [:tr ([:td 1] [:td 2])] -> [:tr [:td 1] [:td 2]] ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; verification-utils @@ -199,19 +122,10 @@ (tree-seq #(and (seqable? %) (not (map? %))) (fn [s] (remove #(or (map? %) (string? %) (keyword? %)) s))) (filter #(and (string? (last %)) (str/includes? (last %) text))))) -(defn nodes-with-exact-text - "Returns a list of nodes from the `tree` that exactly matches text `text` as the last entry of the node. - The tree is assumed to be a valid hiccup-style tree. - - `(nodes-with-text \"the text\" [:svg [:tspan [:text \"the text\"]]]) -> ([:text \"the text\"])`" - [tree text] - (->> tree - (tree-seq #(and (seqable? %) (not (map? %))) (fn [s] (remove #(or (map? %) (string? %) (keyword? %)) s))) - (filter #(and (string? (last %)) (= (last %) text))))) - (defn nodes-with-tag - "Returns a list of nodes from the `tree` that contain an exact match of `tag` as the first entry of the node. - The tag can be any valid hiccup key, but will often be a keyword or a string. The tree is assumed to be a valid hiccup-style tree. + "Returns a list of nodes from the `tree` that contain an exact match of `tag` as the first entry of the node. The tag + can be any valid hiccup key, but will often be a keyword or a string. The tree is assumed to be a valid hiccup-style + tree. `(nodes-with-tag :tspan [:svg [:tspan [:text \"the text\"]]]) -> ([:tspan [:text \"the text\"]])`" [tree tag] @@ -219,18 +133,10 @@ (tree-seq #(and (seqable? %) (not (map? %))) (fn [s] (remove #(or (map? %) (string? %) (keyword? %)) s))) (filter #(#{tag} (first %))))) -(defn remove-attrs - [tree] - (let [matcher (fn [loc] (map? (second (zip/node loc)))) - edit-fn (fn [loc] - (let [[k _m & c] (zip/node loc)] - (zip/replace loc (into [k] c))))] - (edit-nodes tree matcher edit-fn))) - -(defn render-card-as-hickory - "Render the card with `card-id` using the static-viz rendering pipeline as a hickory data structure. - Redefines some internal rendering functions to keep svg from being rendered into a png. - Functions from `hickory.select` can be used on the output of this function and are particularly useful for writing test assertions." +(defn render-card-as-hickory! + "Render the card with `card-id` using the static-viz rendering pipeline as a hickory data structure. Redefines some + internal rendering functions to keep svg from being rendered into a png. Functions from `hickory.select` can be used + on the output of this function and are particularly useful for writing test assertions." [card-id] (let [{:keys [visualization_settings] :as card} (t2/select-one :model/Card :id card-id) query (qp.card/query-for-card card [] nil {:process-viz-settings? true} nil) @@ -247,11 +153,11 @@ hik/parse hik/as-hickory))))) -(defn render-dashcard-as-hickory - "Render the dashcard with `dashcard-id` using the static-viz rendering pipeline as a hickory data structure. - Redefines some internal rendering functions to keep svg from being rendered into a png. - Functions from `hickory.select` can be used on the output of this function and are particularly useful for writing test assertions." - ([dashcard-id] (render-dashcard-as-hickory dashcard-id [])) +(defn render-dashcard-as-hickory! + "Render the dashcard with `dashcard-id` using the static-viz rendering pipeline as a hickory data structure. Redefines + some internal rendering functions to keep svg from being rendered into a png. Functions from `hickory.select` can be + used on the output of this function and are particularly useful for writing test assertions." + ([dashcard-id] (render-dashcard-as-hickory! dashcard-id [])) ([dashcard-id parameters] (let [dashcard (t2/select-one :model/DashboardCard :id dashcard-id) card (t2/select-one :model/Card :id (:card_id dashcard)) diff --git a/test/metabase/pulse/test_util.clj b/test/metabase/pulse/test_util.clj index a75bc38862b217f9b068d2d6d4e2547ea7d141a4..d5725589f9db22fde8654be0de75ed208775dc95 100644 --- a/test/metabase/pulse/test_util.clj +++ b/test/metabase/pulse/test_util.clj @@ -61,26 +61,23 @@ [data] (walk/postwalk identity data)) -(defn do-with-site-url +(defn do-with-site-url! [f] (mt/with-temporary-setting-values [site-url "https://metabase.com/testmb"] (f))) -(defmacro email-test-setup +(defmacro email-test-setup! "Macro that ensures test-data is present and will use a fake inbox for emails" [& body] `(mt/with-fake-inbox - (do-with-site-url (fn [] ~@body)))) + (do-with-site-url! (fn [] ~@body)))) (defmacro slack-test-setup! "Macro that ensures test-data is present and disables sending of all notifications" [& body] - `(with-redefs [channel/send! (fn [& _args#] - :noop) + `(with-redefs [channel/send! (constantly :noop) slack/files-channel (constantly "FOO")] - (do-with-site-url (fn [] ~@body)))) - -(def ^:dynamic *channel-messages* nil) + (do-with-site-url! (fn [] ~@body)))) (defn do-with-captured-channel-send-messages! [thunk] diff --git a/test/metabase/pulse_test.clj b/test/metabase/pulse_test.clj index 724a065a35918857ec908ac13656755b765c7fd3..7698ca629eaeb32af41b6e327d58791bf390a0cb 100644 --- a/test/metabase/pulse_test.clj +++ b/test/metabase/pulse_test.clj @@ -691,7 +691,7 @@ :details {:emails ["nonuser@metabase.com"]}} PulseChannelRecipient _ {:user_id (pulse.test-util/rasta-id) :pulse_channel_id pc-id}] - (pulse.test-util/email-test-setup + (pulse.test-util/email-test-setup! (metabase.pulse/send-pulse! (pulse/retrieve-notification pulse-id)) (is (mt/received-email-body? :rasta #"Manage your subscriptions")) (is (mt/received-email-body? "nonuser@metabase.com" #"Unsubscribe")))))) diff --git a/test/metabase/query_processor/middleware/cache_test.clj b/test/metabase/query_processor/middleware/cache_test.clj index 73f5115667219c07b4d95e86dc771cb70ace19c2..f47713def73cab57f475d78656add4b7af1a749e 100644 --- a/test/metabase/query_processor/middleware/cache_test.clj +++ b/test/metabase/query_processor/middleware/cache_test.clj @@ -89,7 +89,7 @@ (log/tracef "Purge old entries --> store: %s" (pretty/pretty this)) (a/>!! purge-chan ::purge))))) -(defn do-with-mock-cache [f] +(defn do-with-mock-cache! [f] (mt/with-open-channels [save-chan (a/chan 10) purge-chan (a/chan 10)] (mt/with-temporary-setting-values [query-caching-max-ttl 60] @@ -108,8 +108,8 @@ (throw e))))] (f {:save-chan save-chan, :purge-chan purge-chan}))))))) -(defmacro with-mock-cache [[& bindings] & body] - `(do-with-mock-cache (fn [{:keys [~@bindings]}] ~@body))) +(defmacro with-mock-cache! [[& bindings] & body] + `(do-with-mock-cache! (fn [{:keys [~@bindings]}] ~@body))) (def ^:private ^:dynamic ^Long *query-execution-delay-ms* 10) @@ -160,7 +160,7 @@ (deftest is-cacheable-test (testing "something is-cacheable? if it includes `:cache-strategy` and it is not `:nocache`" - (with-mock-cache [] + (with-mock-cache! [] (doseq [[cache-strategy expected] {(ttl-strategy) true {:type :nocache} false nil false}] @@ -174,13 +174,13 @@ (deftest empty-cache-test (testing "if there's nothing in the cache, cached results should *not* be returned" - (with-mock-cache [] + (with-mock-cache! [] (is (= :not-cached (run-query)))))) (deftest return-cached-results-test (testing "if we run the query twice, the second run should return cached results" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (is (= true (cacheable?))) (run-query) @@ -190,7 +190,7 @@ (deftest expired-results-test (testing "If cached resutls are past their TTL, the cached results shouldn't be returned" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (run-query :cache-strategy (assoc (ttl-strategy) :multiplier 0.1)) (mt/wait-for-result save-chan) (Thread/sleep 200) @@ -199,7 +199,7 @@ (deftest ignore-valid-results-when-caching-is-disabled-test (testing "if caching is disabled then cache shouldn't be used even if there's something valid in there" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (run-query) (mt/wait-for-result save-chan) (is (= false @@ -209,7 +209,7 @@ (deftest max-kb-test (testing "check that `query-caching-max-kb` is respected and queries aren't cached if they're past the threshold" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (mt/with-temporary-setting-values [query-caching-max-kb 0] (run-query) (let [result (mt/wait-for-result save-chan)] @@ -225,7 +225,7 @@ "then wait 200 ms, and run `:def`. This should trigger the cache flush for entries past " "`:max-ttl`; and the cached entry for `:abc` should be deleted. Running `:abc` a subsequent time " "should not return cached results") - (with-mock-cache [purge-chan] + (with-mock-cache! [purge-chan] (mt/with-temporary-setting-values [query-caching-max-ttl 0.1] (run-query) (mt/wait-for-result purge-chan) @@ -237,7 +237,7 @@ (deftest ignore-cached-results-test (testing "check that :ignore-cached-results? in middleware is respected when returning results..." - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (run-query :middleware {:ignore-cached-results? false}) (mt/wait-for-result save-chan) (is (= :not-cached @@ -245,7 +245,7 @@ (deftest ignore-cached-results-should-still-save-test (testing "...but if it's set those results should still be cached for next time." - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (is (= true (cacheable?))) (run-query :middleware {:ignore-cached-results? true}) (mt/wait-for-result save-chan) @@ -253,7 +253,7 @@ (deftest min-ttl-test (testing "if the cache takes less than the min TTL to execute, it shouldn't be cached" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (binding [*query-caching-min-ttl* 1000] (run-query) (is (= :metabase.test.util.async/timed-out @@ -262,7 +262,7 @@ (run-query)))))) (testing "...but if it takes *longer* than the min TTL, it should be cached" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (binding [*query-caching-min-ttl* 0.1] (run-query) (mt/wait-for-result save-chan) @@ -271,7 +271,7 @@ (deftest invalid-cache-entry-test (testing "We should handle invalid cache entries gracefully" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (run-query) (mt/wait-for-result save-chan) (let [query-hash (qp.util/query-hash (test-query nil))] @@ -286,7 +286,7 @@ (deftest metadata-test (testing "Verify that correct metadata about caching such as `:updated_at` and `:cached` come back with cached results." - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (mt/with-clock #t "2020-02-19T02:31:07.798Z[UTC]" (run-query) (mt/wait-for-result save-chan) @@ -303,7 +303,7 @@ (testing "Test that the caching middleware actually working in the context of the entire QP" (doseq [query [(mt/mbql-query venues {:order-by [[:asc $id]], :limit 5}) (mt/native-query {:query "SELECT * FROM VENUES ORDER BY ID ASC LIMIT 5;"})]] - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (let [query (assoc query :cache-strategy (ttl-strategy))] (testing (format "query = %s" (pr-str query)) (is (= true @@ -346,7 +346,7 @@ (let [query (assoc (mt/mbql-query venues {:order-by [[:asc $id]] :limit 42}) :cache-strategy (assoc (ttl-strategy) :multiplier 5000)) q-hash (qp.util/query-hash query)] - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (t2/delete! Query :query_hash q-hash) (is (not (:cached (qp/process-query (qp/userland-query query))))) (a/alts!! [save-chan (a/timeout 200)]) ;; wait-for-result closes the channel @@ -367,7 +367,7 @@ (deftest insights-from-cache-test (testing "Insights should work on cached results (#12556)" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (let [query (-> checkins (mt/mbql-query {:breakout [!month.date] :aggregation [[:count]]}) @@ -391,7 +391,7 @@ (deftest export-test (testing "Should be able to cache results streaming as an alternate download format, e.g. csv" - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (let [query (assoc (mt/mbql-query venues {:order-by [[:asc $id]], :limit 6}) :cache-strategy (ttl-strategy))] (with-open [os (java.io.ByteArrayOutputStream.)] @@ -431,7 +431,7 @@ (is (= false (boolean (:cached normal-results))) "Query shouldn't be cached when running without mock cache in place") - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (let [query (assoc query :cache-strategy (ttl-strategy))] (with-open [os (java.io.ByteArrayOutputStream.)] (qp.streaming/do-with-streaming-rff @@ -468,7 +468,7 @@ (mt/with-temp-copy-of-db (mt/with-no-data-perms-for-all-users! (mt/with-test-user :rasta - (with-mock-cache [save-chan] + (with-mock-cache! [save-chan] (letfn [(run-forbidden-query [] (qp/process-query (assoc (mt/mbql-query checkins {:aggregation [[:count]]}) :cache-strategy (ttl-strategy))))] diff --git a/test/metabase/query_processor/middleware/limit_test.clj b/test/metabase/query_processor/middleware/limit_test.clj index e70f4a925dabe1e4538aeb8316f78e17283d6648..5f2b017e0278d28ef1d03fd96a161ebfdafb2b6b 100644 --- a/test/metabase/query_processor/middleware/limit_test.clj +++ b/test/metabase/query_processor/middleware/limit_test.clj @@ -9,7 +9,7 @@ (def ^:private test-max-results 10000) -(defn- limit [query] +(defn- limit! [query] (with-redefs [qp.i/absolute-max-results test-max-results] (let [rff (limit/limit-result-rows query qp.reducible/default-rff) rf (rff {})] @@ -18,7 +18,7 @@ (deftest limit-results-rows-test (testing "Apply to an infinite sequence and make sure it gets capped at `qp.i/absolute-max-results`" (is (= test-max-results - (-> (limit {:type :native}) mt/rows count))))) + (-> (limit! {:type :native}) mt/rows count))))) (deftest ^:parallel disable-max-results-test (testing "Apply `absolute-max-results` limit in the default case" @@ -37,7 +37,7 @@ (deftest max-results-constraint-test (testing "Apply an arbitrary max-results on the query and ensure our results size is appropriately constrained" (is (= 1234 - (-> (limit {:constraints {:max-results 1234} + (-> (limit! {:constraints {:max-results 1234} :type :query :query {:aggregation [[:count]]}}) mt/rows count))))) @@ -47,7 +47,7 @@ (let [query {:constraints {:max-results 46} :type :query :query {}} - result (limit query)] + result (limit! query)] (is (= 46 (-> result mt/rows count)) "number of rows in results should match limit added by middleware") diff --git a/test/metabase/query_processor/middleware/process_userland_query_test.clj b/test/metabase/query_processor/middleware/process_userland_query_test.clj index bc3cbe1c0d813358cb1303664f75d4e1f592fd9f..2af35c2f3645327f91c5f2aebdf2ec6a4731d15d 100644 --- a/test/metabase/query_processor/middleware/process_userland_query_test.clj +++ b/test/metabase/query_processor/middleware/process_userland_query_test.clj @@ -22,7 +22,7 @@ (set! *warn-on-reflection* true) -(defn do-with-query-execution [query run] +(defn do-with-query-execution! [query run] (mt/with-clock #t "2020-02-04T12:22-08:00[US/Pacific]" (let [original-hash (qp.util/query-hash query) result (promise)] @@ -37,7 +37,7 @@ ;; bug that is causing query hashes to get ;; calculated in an inconsistent manner; check ;; `:query` vs `:query-execution-query` - (ex-info (format "%s: Query hashes are not equal!" `do-with-query-execution) + (ex-info (format "%s: Query hashes are not equal!" `do-with-query-execution!) {:query query :original-hash (some-> original-hash codecs/bytes->hex) :query-execution query-execution @@ -51,8 +51,8 @@ (:hash qe) (update :hash (fn [^bytes a-hash] (some-> a-hash codecs/bytes->hex))))))))))) -(defmacro with-query-execution {:style/indent 1} [[qe-result-binding query] & body] - `(do-with-query-execution ~query (fn [~qe-result-binding] ~@body))) +(defmacro with-query-execution! {:style/indent 1} [[qe-result-binding query] & body] + `(do-with-query-execution! ~query (fn [~qe-result-binding] ~@body))) (defn- process-userland-query [query] @@ -71,7 +71,7 @@ (deftest success-test (let [query {:database 2, :type :query, :query {:source-table 26}}] - (with-query-execution [qe query] + (with-query-execution! [qe query] (is (= #t "2020-02-04T12:22:00.000-08:00[US/Pacific]" (t/zoned-date-time)) "sanity check") @@ -108,7 +108,7 @@ (deftest failure-test (let [query {:database 2, :type :query, :query {:source-table 26}}] - (with-query-execution [qe query] + (with-query-execution! [qe query] (binding [qp.pipeline/*run* (fn [_query _rff] (throw (ex-info "Oops!" {:type qp.error-type/qp})))] (is (thrown-with-msg? diff --git a/test/metabase/query_processor/persistence_test.clj b/test/metabase/query_processor/persistence_test.clj index d9a957b4a697f3dc0cda01f8330e0fc662f104bd..37627a56427d9ad6973bdb5b3f0b213681ad9013 100644 --- a/test/metabase/query_processor/persistence_test.clj +++ b/test/metabase/query_processor/persistence_test.clj @@ -70,7 +70,7 @@ (with-redefs [qp.i/absolute-max-results 3] (mt/test-drivers (mt/normal-drivers-with-feature :persist-models) (mt/dataset daily-bird-counts - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (mt/with-temp [Card model {:type :model :database_id (mt/id) :query_type :query @@ -111,7 +111,7 @@ [:native (mt/native-query (qp.compile/compile (mt/mbql-query products)))]]] - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (mt/with-temp [Card model {:type :model :database_id (mt/id) :query_type query-type @@ -153,7 +153,7 @@ (testing "Can use joins with persisted models (#28902)" (mt/test-drivers (mt/normal-drivers-with-feature :persist-models) (mt/dataset test-data - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (mt/with-temp [Card model {:type :model :database_id (mt/id) :query_type :query diff --git a/test/metabase/query_processor/streaming_test.clj b/test/metabase/query_processor/streaming_test.clj index 2ce7d709d527b0b53438aec098d33e4d5f69cbb2..f80c72416139a9b3806dd986db260225fbc9bd6d 100644 --- a/test/metabase/query_processor/streaming_test.clj +++ b/test/metabase/query_processor/streaming_test.clj @@ -320,7 +320,7 @@ user (or user :rasta)] (mt/with-temporary-setting-values [enable-public-sharing true enable-embedding true] - (embed-test/with-new-secret-key + (embed-test/with-new-secret-key! (t2.with-temp/with-temp [Card card (if viz-settings (assoc card-defaults :visualization_settings viz-settings) card-defaults) diff --git a/test/metabase/query_processor/util/relative_datetime_test.clj b/test/metabase/query_processor/util/relative_datetime_test.clj index 5b764637467ebf855853ef86dc07551324f2de25..63b6e3dc127f7996f5cad8d93f21fb6faab0f786 100644 --- a/test/metabase/query_processor/util/relative_datetime_test.clj +++ b/test/metabase/query_processor/util/relative_datetime_test.clj @@ -16,7 +16,7 @@ :params params}) qp/process-query)) -(defn- getdate-vs-ss-ts-test-thunk-generator +(defn- getdate-vs-ss-ts-test-thunk-generator! [unit value] (fn [] ;; `with-redefs` forces use of `gettime()` in :relative-datetime transformation even for units gte or eq to :day. @@ -45,7 +45,7 @@ (mt/test-drivers (mt/normal-drivers-with-feature ::server-side-relative-datetime) (testing "Values of getdate() and server side generated timestamp are equal" (mt/with-metadata-provider (mt/id) - (let [test-thunk (getdate-vs-ss-ts-test-thunk-generator :week -1)] + (let [test-thunk (getdate-vs-ss-ts-test-thunk-generator! :week -1)] (doseq [tz-setter [qp.test-util/do-with-report-timezone-id! test.tz/do-with-system-timezone-id! qp.test-util/do-with-database-timezone-id @@ -64,7 +64,7 @@ (mt/with-database-timezone-id "America/Los_Angeles" (mt/with-report-timezone-id! "America/Los_Angeles" (mt/with-system-timezone-id! "Europe/Prague" - (let [test-thunk (getdate-vs-ss-ts-test-thunk-generator :week -1)] + (let [test-thunk (getdate-vs-ss-ts-test-thunk-generator! :week -1)] (test-thunk)))))))))) (deftest server-side-relative-datetime-various-units-test @@ -73,7 +73,7 @@ (testing "Value of server side generated timestamp matches the one from getdate() with multiple timezone settings" (doseq [unit [:day :week :month :quarter :year] value [-30 0 7] - :let [test-thunk (getdate-vs-ss-ts-test-thunk-generator unit value)]] + :let [test-thunk (getdate-vs-ss-ts-test-thunk-generator! unit value)]] (test-thunk)))))) (deftest server-side-relative-datetime-truncation-test diff --git a/test/metabase/sample_data_test.clj b/test/metabase/sample_data_test.clj index 5a65e2a378e07c587776331531fdaa80d839beab..fae4ac53efe9270b0f1fd8914a297b9206043f95 100644 --- a/test/metabase/sample_data_test.clj +++ b/test/metabase/sample_data_test.clj @@ -171,21 +171,21 @@ (is (not (contains? (get-tables) "NEW_TABLE"))) (jdbc/execute! conn-spec "CREATE TABLE NEW_TABLE (id INTEGER);") (is (contains? (get-tables) "NEW_TABLE")) - (testing "add column" - (is (not (contains? (show-columns-from "NEW_TABLE") "NEW_COLUMN"))) - (jdbc/execute! conn-spec "ALTER TABLE NEW_TABLE ADD COLUMN NEW_COLUMN VARCHAR(255);") - (is (contains? (show-columns-from "NEW_TABLE") "NEW_COLUMN")) - (testing "remove column" - (jdbc/execute! conn-spec "ALTER TABLE NEW_TABLE DROP COLUMN NEW_COLUMN;") + (testing "add column" (is (not (contains? (show-columns-from "NEW_TABLE") "NEW_COLUMN"))) - (testing "drop table" - (jdbc/execute! conn-spec "DROP TABLE NEW_TABLE;") - (is (not (contains? (get-tables) "NEW_TABLE")))))))))))) + (jdbc/execute! conn-spec "ALTER TABLE NEW_TABLE ADD COLUMN NEW_COLUMN VARCHAR(255);") + (is (contains? (show-columns-from "NEW_TABLE") "NEW_COLUMN")) + (testing "remove column" + (jdbc/execute! conn-spec "ALTER TABLE NEW_TABLE DROP COLUMN NEW_COLUMN;") + (is (not (contains? (show-columns-from "NEW_TABLE") "NEW_COLUMN"))) + (testing "drop table" + (jdbc/execute! conn-spec "DROP TABLE NEW_TABLE;") + (is (not (contains? (get-tables) "NEW_TABLE")))))))))))) (deftest sample-database-schedule-sync-test (testing "Check that the sample database has scheduled sync jobs, just like a newly created database" (mt/with-temp-empty-app-db [_conn :h2] - (api.database-test/with-db-scheduler-setup + (api.database-test/with-db-scheduler-setup! (mdb/setup-db! :create-sample-content? true) (sample-data/extract-and-sync-sample-database!) (testing "Sense check: a newly created database should have sync jobs scheduled" diff --git a/test/metabase/sync/analyze_test.clj b/test/metabase/sync/analyze_test.clj index b0fb162c9cbfdb4f48c496ed7fc0cbd2ee27c77b..2ee9c05a93d9fdc81f7f7548bdb68e74efc06b52 100644 --- a/test/metabase/sync/analyze_test.clj +++ b/test/metabase/sync/analyze_test.clj @@ -18,7 +18,7 @@ [metabase.sync.sync-metadata :as sync-metadata] [metabase.test :as mt] [metabase.test.data :as data] - [metabase.test.sync :as test.sync :refer [sync-survives-crash?]] + [metabase.test.sync :as test.sync :refer [sync-survives-crash?!]] [metabase.util :as u] [toucan2.core :as t2] [toucan2.tools.with-temp :as t2.with-temp])) @@ -88,18 +88,18 @@ (deftest survive-fingerprinting-errors (testing "Make sure we survive fingerprinting failing" - (sync-survives-crash? fingerprinters/fingerprinter))) + (sync-survives-crash?! fingerprinters/fingerprinter))) (deftest survive-classify-fields-errors (testing "Make sure we survive field classification failing" - (sync-survives-crash? classifiers.name/semantic-type-for-name-and-base-type) - (sync-survives-crash? classifiers.category/infer-is-category-or-list) - (sync-survives-crash? classifiers.no-preview-display/infer-no-preview-display) - (sync-survives-crash? classifiers.text-fingerprint/infer-semantic-type))) + (sync-survives-crash?! classifiers.name/semantic-type-for-name-and-base-type) + (sync-survives-crash?! classifiers.category/infer-is-category-or-list) + (sync-survives-crash?! classifiers.no-preview-display/infer-no-preview-display) + (sync-survives-crash?! classifiers.text-fingerprint/infer-semantic-type))) (deftest survive-classify-table-errors (testing "Make sure we survive table classification failing" - (sync-survives-crash? classifiers.name/infer-entity-type-by-name))) + (sync-survives-crash?! classifiers.name/infer-entity-type-by-name))) (defn- classified-semantic-type [values] (let [field (mi/instance Field {:base_type :type/Text})] diff --git a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj index cec27b0f6aee197af6c58c8d497004531dd31c04..65b860ce4810a13a5c506287da38498cf063f16b 100644 --- a/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj +++ b/test/metabase/sync/sync_metadata/fields/sync_metadata_test.clj @@ -10,10 +10,10 @@ [toucan2.tools.with-temp :as t2.with-temp])) -(defn- updates-that-will-be-performed +(defn- updates-that-will-be-performed! ([new-metadata-from-sync metadata-in-application-db] ;; use alphabetical field_order by default because the default, database, will update the position - (updates-that-will-be-performed new-metadata-from-sync metadata-in-application-db {:field_order :alphabetical})) + (updates-that-will-be-performed! new-metadata-from-sync metadata-in-application-db {:field_order :alphabetical})) ([new-metadata-from-sync metadata-in-application-db table] (t2.with-temp/with-temp [:model/Table table table] (let [update-operations (atom [])] @@ -29,7 +29,7 @@ (deftest database-type-changed-test (testing "test that if database-type changes we will update it in the DB" (is (= [["Field" 1 {:database_type "Integer"}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -57,7 +57,7 @@ (testing "test that if database-position changes and table.field_order=database we will update the position too" (is (= [["Field" 1 {:database_position 1 :position 1}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! (merge default-metadata {:database-position 1}) (merge default-metadata {:database-position 0 :position 0 @@ -65,7 +65,7 @@ {:field_order :database}))) (testing "but not if the table's fields should not be sorted according to the database" (is (= [["Field" 1 {:database_position 1}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! (merge default-metadata {:database-position 1}) (merge default-metadata {:database-position 0 :position 0 @@ -75,7 +75,7 @@ (deftest database-required-changed-test (testing "test that if database-required changes we will update it in the DB" (is (= [["Field" 1 {:database_required false}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -94,7 +94,7 @@ (deftest database-is-auto-increment-changed-test (testing "test that if database-required changes we will update it in the DB" (is (= [["Field" 1 {:database_is_auto_increment true}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -109,7 +109,7 @@ :database-required false :database-is-auto-increment false}))) (is (= [["Field" 1 {:database_is_auto_increment false}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -127,7 +127,7 @@ (deftest json-unfolding-test (testing "test that if json-unfolding changes the DB doesn't get updated" (is (= [] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -147,7 +147,7 @@ (deftest no-op-test (testing "no changes should be made (i.e., no calls to `update!`) if nothing changes" (is (= [] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -167,13 +167,13 @@ (deftest update-database-partitioned-test (testing "update from nil -> boolean" (is (= [["Field" 1 {:database_partitioned false}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! (merge default-metadata {:database-partitioned false}) (merge default-metadata {:database-partitioned nil :id 1}))))) (testing "flip the state" (is (= [["Field" 1 {:database_partitioned false}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! (merge default-metadata {:database-partitioned false}) (merge default-metadata {:database-partitioned true :id 1})))))) @@ -182,7 +182,7 @@ "to set a `nil` value in the DB -- this is against the rules -- we should set `NULL` instead. See " "`TableMetadataField` schema.") (is (= [["Field" 1 {:database_type "NULL"}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type nil :base-type :type/Integer @@ -202,7 +202,7 @@ (testing (str "if `database-type` comes back as `nil` and was already saved in application DB as `NULL` no changes " "should be made") (is (= [] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type nil :base-type :type/Integer @@ -222,7 +222,7 @@ (deftest dont-overwrite-semantic-type-test (testing "We should not override non-nil `semantic_type`s" (is (= [] - (updates-that-will-be-performed + (updates-that-will-be-performed! {:name "My Field" :database-type "Integer" :base-type :type/Integer @@ -252,7 +252,7 @@ :fingerprint_version 0 :fingerprint nil :semantic_type nil}]] - (updates-that-will-be-performed + (updates-that-will-be-performed! (merge default-metadata {:id 1 :base-type :type/Text diff --git a/test/metabase/sync/sync_metadata_test.clj b/test/metabase/sync/sync_metadata_test.clj index f3f9d9928eb9508dad71348fbf6ddc884876f99c..517d22f01fa6ea0a1b41a704d3c64d5d03d36b9b 100644 --- a/test/metabase/sync/sync_metadata_test.clj +++ b/test/metabase/sync/sync_metadata_test.clj @@ -6,26 +6,26 @@ [metabase.sync.sync-metadata.metabase-metadata :as metabase-metadata] [metabase.sync.sync-metadata.sync-timezone :as sync-tz] [metabase.sync.sync-metadata.tables :as sync-tables] - [metabase.test.sync :refer [sync-survives-crash?]])) + [metabase.test.sync :refer [sync-survives-crash?!]])) (deftest survive-metadata-errors (testing "Make sure we survive metadata sync failing" - (sync-survives-crash? metabase-metadata/sync-metabase-metadata!))) + (sync-survives-crash?! metabase-metadata/sync-metabase-metadata!))) (deftest survive-tz-errors (testing "Make sure we survive metadataDB timezone sync failing" - (sync-survives-crash? sync-tz/sync-timezone!))) + (sync-survives-crash?! sync-tz/sync-timezone!))) (deftest survive-fields-errors (testing "Make sure we survive field sync failing" - (sync-survives-crash? sync-fields/sync-and-update!))) + (sync-survives-crash?! sync-fields/sync-and-update!))) (deftest survive-table-errors (testing "Make sure we survive table sync failing" - (sync-survives-crash? sync-tables/create-or-reactivate-tables!) - (sync-survives-crash? sync-tables/retire-tables!) - (sync-survives-crash? sync-tables/update-tables-metadata-if-needed!))) + (sync-survives-crash?! sync-tables/create-or-reactivate-tables!) + (sync-survives-crash?! sync-tables/retire-tables!) + (sync-survives-crash?! sync-tables/update-tables-metadata-if-needed!))) (deftest survive-fk-errors (testing "Make sure we survive FK sync failing" - (sync-survives-crash? sync-fks/mark-fk!))) + (sync-survives-crash?! sync-fks/mark-fk!))) diff --git a/test/metabase/task/persist_refresh_test.clj b/test/metabase/task/persist_refresh_test.clj index 6f647afab7e6cdcd160a7392e2b209aff8c52173..1039734e1b2142da50c3bcf4b183473db34243a4 100644 --- a/test/metabase/task/persist_refresh_test.clj +++ b/test/metabase/task/persist_refresh_test.clj @@ -82,7 +82,7 @@ (select-keys (task.persist-refresh/job-info-by-db-id) ids)))) (deftest reschedule-refresh-test - (mt/with-temp-scheduler + (mt/with-temp-scheduler! (mt/with-temp [Database db-1 {:settings {:persist-models-enabled true}} Database db-2 {:settings {:persist-models-enabled true}}] (#'task.persist-refresh/job-init!) diff --git a/test/metabase/task/sync_databases_test.clj b/test/metabase/task/sync_databases_test.clj index 7ec9d1d990ae0bc225acc5b517750d6912c9ba0a..3047b51659d492a56622e41305add34331d298ba 100644 --- a/test/metabase/task/sync_databases_test.clj +++ b/test/metabase/task/sync_databases_test.clj @@ -69,8 +69,8 @@ (update :triggers (partial filter #(str/ends-with? (:key %) (str \. (u/the-id db-or-id))))) (dissoc :class))))) -(defmacro with-scheduler-setup [& body] - `(tu/with-temp-scheduler +(defmacro ^:private with-scheduler-setup! [& body] + `(tu/with-temp-scheduler! (#'task.sync-databases/job-init) ~@body)) @@ -93,7 +93,7 @@ ;; Check that a newly created database automatically gets scheduled (deftest new-db-jobs-scheduled-test (is (= [sync-job fv-job] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] (current-tasks-for-db database)))))) @@ -101,7 +101,7 @@ (deftest custom-schedule-test (is (= [(assoc-in sync-job [:triggers 0 :cron-schedule] "0 30 4,16 * * ? *") (assoc-in fv-job [:triggers 0 :cron-schedule] "0 15 10 ? * 6#3")] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true} :metadata_sync_schedule "0 30 4,16 * * ? *" ; 4:30 AM and PM daily :cache_field_values_schedule "0 15 10 ? * 6#3"}] ; 10:15 on the 3rd Friday of the Month @@ -111,7 +111,7 @@ (deftest unschedule-deleted-database-test (is (= [(update sync-job :triggers empty) (update fv-job :triggers empty)] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] (t2/delete! Database :id (u/the-id database)) (current-tasks-for-db database)))))) @@ -120,7 +120,7 @@ (deftest schedule-change-test (is (= [(assoc-in sync-job [:triggers 0 :cron-schedule] "0 15 10 ? * MON-FRI") (assoc-in fv-job [:triggers 0 :cron-schedule] "0 11 11 11 11 ?")] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] (t2/update! Database (u/the-id database) {:metadata_sync_schedule "0 15 10 ? * MON-FRI" ; 10:15 AM every weekday @@ -131,7 +131,7 @@ (deftest schedule-changes-only-expected-test (is (= [sync-job (assoc-in fv-job [:triggers 0 :cron-schedule] "0 15 10 ? * MON-FRI")] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] (t2/update! Database (u/the-id database) {:cache_field_values_schedule "0 15 10 ? * MON-FRI"}) @@ -139,7 +139,7 @@ (is (= [(assoc-in sync-job [:triggers 0 :cron-schedule] "0 15 10 ? * MON-FRI") fv-job] - (with-scheduler-setup + (with-scheduler-setup! (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] (t2/update! Database (u/the-id database) {:metadata_sync_schedule "0 15 10 ? * MON-FRI"}) @@ -177,7 +177,7 @@ (deftest check-orphaned-jobs-removed-test (testing "jobs for orphaned databases are removed during sync run" - (with-scheduler-setup + (with-scheduler-setup! (doseq [sync-fn [#'task.sync-databases/update-field-values! #'task.sync-databases/sync-and-analyze-database!]] (testing (str sync-fn) (t2.with-temp/with-temp [Database database {:details {:let-user-control-scheduling true}}] diff --git a/test/metabase/task_test.clj b/test/metabase/task_test.clj index 76fbea1734de298438e862552739d7e29850a26c..528161ea747bb560388207b9bd10774efba4d359 100644 --- a/test/metabase/task_test.clj +++ b/test/metabase/task_test.clj @@ -47,15 +47,15 @@ (cron/cron-schedule "0 0 6 * * ? *") ; at 6 AM every day (cron/with-misfire-handling-instruction-ignore-misfires))))) -(defn- do-with-temp-scheduler-and-cleanup [f] - (mt/with-temp-scheduler +(defn- do-with-temp-scheduler-and-cleanup! [f] + (mt/with-temp-scheduler! (try (f) (finally (task/delete-task! (.getKey (job)) (.getKey (trigger-1))))))) -(defmacro ^:private with-temp-scheduler-and-cleanup [& body] - `(do-with-temp-scheduler-and-cleanup (fn [] ~@body))) +(defmacro ^:private with-temp-scheduler-and-cleanup! [& body] + `(do-with-temp-scheduler-and-cleanup! (fn [] ~@body))) (defn- triggers [] (set @@ -65,7 +65,7 @@ (deftest schedule-job-test (testing "can we schedule a job?" - (with-temp-scheduler-and-cleanup + (with-temp-scheduler-and-cleanup! (task/schedule-task! (job) (trigger-1)) (is (= #{{:cron-expression "0 0 * * * ? *" :misfire-instruction CronTrigger/MISFIRE_INSTRUCTION_DO_NOTHING}} @@ -73,7 +73,7 @@ (deftest reschedule-job-test (testing "does scheduling a job a second time work without throwing errors?" - (with-temp-scheduler-and-cleanup + (with-temp-scheduler-and-cleanup! (task/schedule-task! (job) (trigger-1)) (task/schedule-task! (job) (trigger-1)) (is (= #{{:cron-expression "0 0 * * * ? *" @@ -82,7 +82,7 @@ (deftest reschedule-and-replace-job-test (testing "does scheduling a job with a *new* trigger replace the original? (can we reschedule a job?)" - (with-temp-scheduler-and-cleanup + (with-temp-scheduler-and-cleanup! (task/schedule-task! (job) (trigger-1)) (task/schedule-task! (job) (trigger-2)) (is (= #{{:cron-expression "0 0 6 * * ? *" @@ -91,7 +91,7 @@ (deftest scheduler-info-test (testing "Make sure scheduler-info doesn't explode and returns info in the general shape we expect" - (mt/with-temp-scheduler + (mt/with-temp-scheduler! (is (malli= [:map {:closed true} [:scheduler [:+ :string]] [:jobs [:sequential @@ -111,7 +111,7 @@ (task/scheduler-info)))))) (deftest start-scheduler-no-op-with-env-var-test - (tu/do-with-unstarted-temp-scheduler + (tu/do-with-unstarted-temp-scheduler! (^:once fn* [] (testing (format "task/start-scheduler! should no-op When MB_DISABLE_SCHEDULER is set") (testing "Sanity check" diff --git a/test/metabase/test.clj b/test/metabase/test.clj index ace9084b425030959c81bf720303a8fbdf572bba..4351db67c9b21933fcdc10d550f2c0af7b25560b 100644 --- a/test/metabase/test.clj +++ b/test/metabase/test.clj @@ -154,7 +154,7 @@ client-real-response] [i18n.tu - with-mock-i18n-bundles + with-mock-i18n-bundles! with-user-locale] [initialize @@ -214,7 +214,7 @@ derecordize] [test.persistence - with-persistence-enabled] + with-persistence-enabled!] [test.users fetch-user @@ -266,7 +266,7 @@ with-temp-env-var-value! with-temp-dir with-temp-file - with-temp-scheduler + with-temp-scheduler! with-temp-vals-in-db with-temporary-setting-values with-temporary-raw-setting-values diff --git a/test/metabase/test/data.clj b/test/metabase/test/data.clj index 9874dd1356aa22f3358f921f094c2765732fd201..14d1f7c03de4d5a8f0ec0e3540ee1f6bf2e73487 100644 --- a/test/metabase/test/data.clj +++ b/test/metabase/test/data.clj @@ -266,6 +266,8 @@ [& body] `(data.impl/do-with-temp-copy-of-db (^:once fn* [] ~@body))) +;;; TODO FIXME -- rename this to `with-empty-h2-app-db` +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-empty-h2-app-db "Runs `body` under a new, blank, H2 application database (randomly named), in which all model tables have been created via Liquibase schema migrations. After `body` is finished, the original app DB bindings are restored. diff --git a/test/metabase/test/data/impl/get_or_create.clj b/test/metabase/test/data/impl/get_or_create.clj index 0369c8adb301f582d18295877fe0d3f256f9fbbd..fa702ab7c61ed660641b6c6936b35596e1841338 100644 --- a/test/metabase/test/data/impl/get_or_create.clj +++ b/test/metabase/test/data/impl/get_or_create.clj @@ -289,7 +289,7 @@ ;; ;; require/resolve used here to avoid circular refs (if (driver/report-timezone) - ((requiring-resolve 'metabase.test.util/do-with-temporary-setting-value) + ((requiring-resolve 'metabase.test.util/do-with-temporary-setting-value!) :report-timezone nil thunk) (thunk)))) diff --git a/test/metabase/test/integrations/ldap.clj b/test/metabase/test/integrations/ldap.clj index b9f76d03037276ff374b582ae93df6b67ad882ac..15a98c7caa355da1aac476c013ed877160d5106a 100644 --- a/test/metabase/test/integrations/ldap.clj +++ b/test/metabase/test/integrations/ldap.clj @@ -53,7 +53,7 @@ (u/varargs Schema [(Schema/getDefaultStandardSchema) (Schema/getSchema (u/varargs File [(io/file "test_resources/posixGroup.schema.ldif")]))]))) -(defn do-with-ldap-server +(defn do-with-ldap-server! "Bind `*ldap-server*` and the relevant settings to an in-memory LDAP testing server and executes `f`." [f options] (binding [*ldap-server* (start-ldap-server! options)] @@ -69,17 +69,17 @@ (f))) (finally (.shutDown *ldap-server* true))))) -(defmacro with-ldap-server +(defmacro with-ldap-server! "Bind `*ldap-server*` and the relevant settings to an in-memory LDAP testing server and executes `body`." [& body] - `(do-with-ldap-server (fn [] ~@body) - {:ldif-resource "ldap.ldif" - :schema (get-default-schema)})) + `(do-with-ldap-server! (fn [] ~@body) + {:ldif-resource "ldap.ldif" + :schema (get-default-schema)})) -(defmacro with-active-directory-ldap-server +(defmacro with-active-directory-ldap-server! "Bind `*ldap-server*` and the relevant settings to an in-memory LDAP testing server and executes `body`. This version of the macro uses options that simulate an Active Directory server with memberOf attributes." [& body] - `(do-with-ldap-server (fn [] ~@body) - {:ldif-resource "active_directory.ldif" - :schema nil})) + `(do-with-ldap-server! (fn [] ~@body) + {:ldif-resource "active_directory.ldif" + :schema nil})) diff --git a/test/metabase/test/persistence.clj b/test/metabase/test/persistence.clj index d9e4e57ae1eaa98ee77b5ab4463791c38d06780e..6a61e072ebe3d2d5e389e5e48c04c3c17feb65a9 100644 --- a/test/metabase/test/persistence.clj +++ b/test/metabase/test/persistence.clj @@ -6,7 +6,7 @@ [metabase.test.data :as data] [metabase.test.util :as tu])) -(defn with-persistence-enabled* +(defn do-with-persistence-enabled! [f] (tu/with-temporary-setting-values [:persisted-models-enabled true] (ddl.i/check-can-persist (data/db)) @@ -17,7 +17,7 @@ (var-get #'task.persist-refresh/dispatching-refresher)))] (f persist-fn)))) -(defmacro with-persistence-enabled +(defmacro with-persistence-enabled! "Does the necessary setup to enable persistence on the current db. Provide a binding for a function to persist everything. @@ -30,4 +30,4 @@ (persist-models!)) ...))" [[persist-fn-binding] & body] - `(with-persistence-enabled* (fn [~persist-fn-binding] ~@body))) + `(do-with-persistence-enabled! (fn [~persist-fn-binding] ~@body))) diff --git a/test/metabase/test/sync.clj b/test/metabase/test/sync.clj index 9e31e50c9ccaa35d71a79e66cffb3a099b7c34b9..efb2c9f87db5558827fd89bb6c24119bb2cdcc14 100644 --- a/test/metabase/test/sync.clj +++ b/test/metabase/test/sync.clj @@ -20,7 +20,7 @@ [& _] (throw (Exception. "simulated exception"))) -(defmacro sync-survives-crash? +(defmacro sync-survives-crash?! "Can sync process survive `f` crashing?" [f] `(is (= (sync-steps-run-to-completion) diff --git a/test/metabase/test/util.clj b/test/metabase/test/util.clj index 683ba02f43ed16f6fab041abefcb36f2b29608b6..a39fb311271016c7ae70280db9ab422b41015613 100644 --- a/test/metabase/test/util.clj +++ b/test/metabase/test/util.clj @@ -403,7 +403,7 @@ (t2/delete! Setting :key setting-k)) (setting.cache/restore-cache!)) -(defn do-with-temporary-setting-value +(defn do-with-temporary-setting-value! "Temporarily set the value of the Setting named by keyword `setting-k` to `value` and execute `f`, then re-establish the original value. This works much the same way as [[binding]]. @@ -462,6 +462,8 @@ :original-value original-value} e)))))))))) +;;; TODO FIXME -- either rename this to `with-temporary-setting-values!` or fix it and make it thread-safe +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-temporary-setting-values "Temporarily bind the site-wide values of one or more `Settings`, execute body, and re-establish the original values. This works much the same way as `binding`. @@ -475,11 +477,13 @@ (assert (even? (count bindings)) "mismatched setting/value pairs: is each setting name followed by a value?") (if (empty? bindings) `(do ~@body) - `(do-with-temporary-setting-value ~(keyword setting-k) ~value + `(do-with-temporary-setting-value! ~(keyword setting-k) ~value (fn [] (with-temporary-setting-values ~more ~@body))))) +;;; TODO FIXME -- either rename this to `with-temporary-raw-setting-values!` or fix it and make it thread-safe +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-temporary-raw-setting-values "Like [[with-temporary-setting-values]] but works with raw value and it allows settings that are not defined using [[metabase.models.setting/defsetting]]." @@ -487,22 +491,24 @@ (assert (even? (count bindings)) "mismatched setting/value pairs: is each setting name followed by a value?") (if (empty? bindings) `(do ~@body) - `(do-with-temporary-setting-value ~(keyword setting-k) ~value + `(do-with-temporary-setting-value! ~(keyword setting-k) ~value (fn [] (with-temporary-raw-setting-values ~more ~@body)) :raw-setting? true))) -(defn do-with-discarded-setting-changes [settings thunk] +(defn do-with-discarded-setting-changes! [settings thunk] (initialize/initialize-if-needed! :db :plugins) ((reduce (fn [thunk setting-k] (fn [] (let [value (setting/read-setting setting-k)] - (do-with-temporary-setting-value setting-k value thunk :skip-init? true)))) + (do-with-temporary-setting-value! setting-k value thunk :skip-init? true)))) thunk settings))) +;;; TODO FIXME -- either rename this to `with-discarded-setting-changes!` or fix it and make it thread-safe +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro discard-setting-changes "Execute `body` in a try-finally block, restoring any changes to listed `settings` to their original values at its conclusion. @@ -511,7 +517,7 @@ ...)" {:style/indent 1} [settings & body] - `(do-with-discarded-setting-changes ~(mapv keyword settings) (fn [] ~@body))) + `(do-with-discarded-setting-changes! ~(mapv keyword settings) (fn [] ~@body))) (defn- maybe-merge-original-values "For some map columns like `Database.settings` or `User.settings`, merge the original values with the temp ones to @@ -630,7 +636,7 @@ (.setProperty StdSchedulerFactory/PROP_JOB_STORE_CLASS (.getCanonicalName org.quartz.simpl.RAMJobStore)) (.setProperty (str StdSchedulerFactory/PROP_THREAD_POOL_PREFIX ".threadCount") (str in-memory-scheduler-thread-count)))))) -(defn do-with-unstarted-temp-scheduler [thunk] +(defn do-with-unstarted-temp-scheduler! [thunk] (let [temp-scheduler (in-memory-scheduler) already-bound? (identical? @task/*quartz-scheduler* temp-scheduler)] (if already-bound? @@ -647,16 +653,16 @@ (finally (qs/shutdown temp-scheduler)))))) -(defn do-with-temp-scheduler [thunk] +(defn do-with-temp-scheduler! [thunk] ;; not 100% sure we need to initialize the DB anymore since the temp scheduler is in-memory-only now. (classloader/the-classloader) (initialize/initialize-if-needed! :db) - (do-with-unstarted-temp-scheduler + (do-with-unstarted-temp-scheduler! (^:once fn* [] (qs/start @task/*quartz-scheduler*) (thunk)))) -(defmacro with-temp-scheduler +(defmacro with-temp-scheduler! "Execute `body` with a temporary scheduler in place. This does not initialize the all the jobs for performance reasons, so make sure you init it yourself! @@ -667,7 +673,7 @@ (scheduler-current-tasks))" {:style/indent 0} [& body] - `(do-with-temp-scheduler (fn [] ~@body))) + `(do-with-temp-scheduler! (fn [] ~@body))) (defn scheduler-current-tasks "Return information about the currently scheduled tasks (jobs+triggers) for the current scheduler. Intended so we @@ -1097,7 +1103,9 @@ [locale-tag & body] `(call-with-locale ~locale-tag (fn [] ~@body))) -(defn do-with-column-remappings [orig->remapped thunk] +;;; TODO -- this could be made thread-safe if we made [[with-temp-vals-in-db]] thread-safe which I think is pretty +;;; doable (just do it in a transaction?) +(defn do-with-column-remappings! [orig->remapped thunk] (transduce identity (fn @@ -1160,6 +1168,8 @@ :else x)) +;;; TODO FIXME -- either rename this to `with-column-remappings!` or fix it and make it thread-safe. +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-column-remappings "Execute `body` with column remappings in place. Can create either FK \"external\" or human-readable-values \"internal\" remappings: @@ -1191,7 +1201,7 @@ ...)" {:arglists '([[original-col source-col & more-remappings] & body])} [cols & body] - `(do-with-column-remappings + `(do-with-column-remappings! ~(into {} (comp (map col-remappings-arg) (partition-all 2)) cols) @@ -1205,7 +1215,8 @@ (with-open [socket (ServerSocket. 0)] (.getLocalPort socket))) -(defn do-with-env-keys-renamed-by +;;; TODO -- we could make this thread-safe if we introduced a new dynamic variable to replace [[env/env]] +(defn do-with-env-keys-renamed-by! "Evaluates the thunk with the current core.environ/env being redefined, its keys having been renamed by the given rename-fn. Prefer to use the with-env-keys-renamed-by macro version instead." [rename-fn thunk] @@ -1222,12 +1233,14 @@ (with-redefs [env/env new-e] (thunk))))) +;;; TODO FIXME -- either rename this to `with-env-keys-renamed-by!` or fix it and make it thread-safe +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defmacro with-env-keys-renamed-by "Evaluates body with the current core.environ/env being redefined, its keys having been renamed by the given rename-fn." {:arglists '([rename-fn & body])} [rename-fn & body] - `(do-with-env-keys-renamed-by ~rename-fn (fn [] ~@body))) + `(do-with-env-keys-renamed-by! ~rename-fn (fn [] ~@body))) (defn do-with-temp-file "Impl for `with-temp-file` macro." diff --git a/test/metabase/test/util/i18n.clj b/test/metabase/test/util/i18n.clj index fd721f52327018a48fee854a2188707c765611fe..adcbe01fee33fad50c32dd3a154c5bc8b583357a 100644 --- a/test/metabase/test/util/i18n.clj +++ b/test/metabase/test/util/i18n.clj @@ -4,24 +4,25 @@ [metabase.util.i18n :as i18n] [metabase.util.i18n.impl :as i18n.impl])) -(defn do-with-mock-i18n-bundles [bundles thunk] +;;; TODO -- this could be made thread-safe pretty easily if we made [[i18n.impl/translations]] dynamic +(defn do-with-mock-i18n-bundles! [bundles thunk] (t/testing (format "\nwith mock i18n bundles %s\n" (pr-str bundles)) (let [locale->bundle (into {} (for [[locale-name bundle] bundles] [(i18n/locale locale-name) bundle]))] (with-redefs [i18n.impl/translations (comp locale->bundle i18n/locale)] (thunk))))) -(defmacro with-mock-i18n-bundles +(defmacro with-mock-i18n-bundles! "Mock the i18n resource bundles for the duration of `body`. - (with-mock-i18n-bundles {\"es\" {:messages {\"Your database has been added!\" + (with-mock-i18n-bundles! {\"es\" {:messages {\"Your database has been added!\" [\"¡Tu base de datos ha sido añadida!\"]}} \"es-MX\" {:messages {\"I''m good thanks\" [\"Está bien, gracias\"]}}} (translate \"es-MX\" \"Your database has been added!\")) ;; -> \"¡Tu base de datos ha sido añadida!\"" [bundles & body] - `(do-with-mock-i18n-bundles ~bundles (fn [] ~@body))) + `(do-with-mock-i18n-bundles! ~bundles (fn [] ~@body))) (defmacro with-user-locale [user-locale & body] diff --git a/test/metabase/test/util/public_settings.clj b/test/metabase/test/util/public_settings.clj index 1fd7a10539a2cc785a7fd6da207ddde0769c84c1..4a5637d67fd33ebb9d91add4c0c23c0384c4b383 100644 --- a/test/metabase/test/util/public_settings.clj +++ b/test/metabase/test/util/public_settings.clj @@ -5,6 +5,8 @@ [metabase.public-settings.premium-features :as premium-features] [metabase.test.util.thread-local :as tu.thread-local])) +;;; This is actually thread-safe by default unless you're using [[metabase.test/test-helpers-set-global-values!]] +#_{:clj-kondo/ignore [:metabase/test-helpers-use-non-thread-safe-functions]} (defn do-with-premium-features [features thunk] (let [features (set (map name features))] diff --git a/test/metabase/upload_test.clj b/test/metabase/upload_test.clj index 9230bbb74968cd7533fb3a40aaaef413c57d6e6e..a7d538010a5a2c978cf9747c13f1a17bb8ac82b4 100644 --- a/test/metabase/upload_test.clj +++ b/test/metabase/upload_test.clj @@ -1787,7 +1787,7 @@ (is (= #{question-id model-id complex-model-id} (into #{} (map :id) (t2/select :model/Card :table_id table-id :archived false)))) - (mt/with-persistence-enabled [persist-models!] + (mt/with-persistence-enabled! [persist-models!] (persist-models!) (let [cached-before (cached-model-ids) diff --git a/test/metabase/util/i18n/impl_test.clj b/test/metabase/util/i18n/impl_test.clj index 97f2f2287817506593680190bc528aa36cc10b36..c3ee80df6a3328d993f1c747604c62619b81b967 100644 --- a/test/metabase/util/i18n/impl_test.clj +++ b/test/metabase/util/i18n/impl_test.clj @@ -87,7 +87,7 @@ (i18n.impl/translate "zz" "Translate me {0}" [100]))))) (deftest translate-test - (mt/with-mock-i18n-bundles {"es" {:messages + (mt/with-mock-i18n-bundles! {"es" {:messages {"Your database has been added!" "¡Tu base de datos ha sido añadida!" "I''m good thanks" "Está bien, gracias" "must be {0} characters or less" "deben tener {0} caracteres o menos"}} @@ -115,7 +115,7 @@ (i18n.impl/translate "es" "must be {0} characters or less" [140])))))) (deftest translate-error-handling-test - (mt/with-mock-i18n-bundles {"ba-DD" {"Bad translation {0}" "BaD TrAnSlAtIoN {a}"}} + (mt/with-mock-i18n-bundles! {"ba-DD" {"Bad translation {0}" "BaD TrAnSlAtIoN {a}"}} (testing "Should fall back to original format string if translated one is busted" (is (= "Bad translation 100" (i18n.impl/translate "ba-DD" "Bad translation {0}" [100])))) diff --git a/test/metabase/util/i18n_test.clj b/test/metabase/util/i18n_test.clj index 1e5d0c53486c82e33f0169e5e2e93da63f5dfb8a..3cce07605ee6ff4485ae607d9f6feef6163e8673 100644 --- a/test/metabase/util/i18n_test.clj +++ b/test/metabase/util/i18n_test.clj @@ -11,7 +11,7 @@ ["pt_BR", "Portuguese (Brazil)"])))) (deftest tru-test - (mt/with-mock-i18n-bundles {"es" {:messages {"must be {0} characters or less" + (mt/with-mock-i18n-bundles! {"es" {:messages {"must be {0} characters or less" "deben tener {0} caracteres o menos"}}} (doseq [[message f] {"tru" (fn [] (i18n/tru "must be {0} characters or less" 140)) @@ -43,7 +43,7 @@ (f))))))))))) (deftest trs-test - (mt/with-mock-i18n-bundles {"es" {:messages {"must be {0} characters or less" + (mt/with-mock-i18n-bundles! {"es" {:messages {"must be {0} characters or less" "deben tener {0} caracteres o menos"}}} (doseq [[message f] {"trs" (fn [] (i18n/trs "must be {0} characters or less" 140)) @@ -70,7 +70,7 @@ (f))))))))))) (deftest trun-test - (mt/with-mock-i18n-bundles {"es" {:headers {"Plural-Forms" "nplurals=2; plural=(n != 1);\n"} + (mt/with-mock-i18n-bundles! {"es" {:headers {"Plural-Forms" "nplurals=2; plural=(n != 1);\n"} :messages {"{0} table" ["{0} tabla" "{0} tablas"]}}} (doseq [[message f] {"trun" @@ -102,7 +102,7 @@ (deftest trsn-test - (mt/with-mock-i18n-bundles {"es" {:headers {"Plural-Forms" "nplurals=2; plural=(n != 1);\n"} + (mt/with-mock-i18n-bundles! {"es" {:headers {"Plural-Forms" "nplurals=2; plural=(n != 1);\n"} :messages {"{0} table" ["{0} tabla" "{0} tablas"]}}} (doseq [[message f] {"trsn - singular" diff --git a/test/metabase/util/malli_test.clj b/test/metabase/util/malli_test.clj index 2be87ea2e4240cd10dcb3fa8f342d1dca0c63554..e1c01532699575a68d278941abafa69f42631e4c 100644 --- a/test/metabase/util/malli_test.clj +++ b/test/metabase/util/malli_test.clj @@ -27,7 +27,7 @@ (is (= "Special Number that has to be less than four description" (umd/describe special-lt-4-schema))) - (mt/with-mock-i18n-bundles {"es" {:messages {"Special Number that has to be less than four description" + (mt/with-mock-i18n-bundles! {"es" {:messages {"Special Number that has to be less than four description" "Número especial que tiene que ser menos de cuatro descripción" "Special Number that has to be less than four error"