From 0bf94fa60f8a020c071b91ce678cbe783551ae00 Mon Sep 17 00:00:00 2001 From: gered Date: Tue, 31 May 2016 18:20:09 -0400 Subject: [PATCH] add tests --- project.clj | 12 ++-- src/views/sql/core.clj | 2 +- test/views/sql/sql_analysis_tests.clj | 51 ++++++++++++++ test/views/sql/test_fixtures.clj | 16 +++++ test/views/sql/vexec_tests.clj | 78 ++++++++++++++++++++++ test/views/sql/view_test.clj | 96 +++++++++++++++++++++++++++ 6 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 test/views/sql/sql_analysis_tests.clj create mode 100644 test/views/sql/test_fixtures.clj create mode 100644 test/views/sql/vexec_tests.clj create mode 100644 test/views/sql/view_test.clj diff --git a/project.clj b/project.clj index 80039da..056d7db 100644 --- a/project.clj +++ b/project.clj @@ -9,7 +9,11 @@ [com.github.jsqlparser/jsqlparser "0.9.5"]] :profiles {:provided - {:dependencies - [[org.clojure/clojure "1.8.0"] - [org.clojure/java.jdbc "0.6.1"] - [gered/views "1.5-SNAPSHOT"]]}}) + {:dependencies [[org.clojure/clojure "1.8.0"] + [org.clojure/java.jdbc "0.6.1"] + [gered/views "1.5-SNAPSHOT"]]} + + :test + {:dependencies [[pjstadig/humane-test-output "0.8.0"]] + :injections [(require 'pjstadig.humane-test-output) + (pjstadig.humane-test-output/activate!)]}}) diff --git a/src/views/sql/core.clj b/src/views/sql/core.clj index 15e2782..b58e62a 100644 --- a/src/views/sql/core.clj +++ b/src/views/sql/core.clj @@ -48,7 +48,7 @@ (if-not stmt-type (throw (new Exception "Unsupported SQL query. Only SELECT, INSERT, UPDATE and DELETE queries are supported!")) {:type stmt-type - :returning? (sql-stmt-returning? stmt stmt-type) + :returning? (boolean (sql-stmt-returning? stmt stmt-type)) :tables (get-query-tables-set stmt)}))) (defonce query-info-cache (atom {})) diff --git a/test/views/sql/sql_analysis_tests.clj b/test/views/sql/sql_analysis_tests.clj new file mode 100644 index 0000000..d6a9797 --- /dev/null +++ b/test/views/sql/sql_analysis_tests.clj @@ -0,0 +1,51 @@ +(ns views.sql.sql-analysis-tests + (:use + clojure.test + views.sql.test-fixtures + views.sql.core)) + +(use-fixtures :each clear-query-cache-fixture) + + +;; NOTE: the purpose of the tests which look at analysis results of SQL queries +;; is __NOT__ to test how good the support of JSqlParser is, but just +;; to ensure that the views.sql abstraction over it works. + +(deftest select-query-analyzed-correctly + (let [info (query-info "SELECT * FROM book WHERE price > 100.00 ORDER BY title")] + (is (= :select (:type info))) + (is (= true (:returning? info))) + (is (= #{:book} (:tables info))))) + +(deftest insert-query-analyzed-correctly + (let [info (query-info "INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL)")] + (is (= :insert (:type info))) + (is (= false (:returning? info))) + (is (= #{:example} (:tables info))))) + +(deftest insert-query-with-returning-clause-analyzed-correctly + (let [info (query-info "INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL) RETURNING *")] + (is (= :insert (:type info))) + (is (= true (:returning? info))) + (is (= #{:example} (:tables info))))) + +(deftest update-query-analyzed-correctly + (let [info (query-info "UPDATE example SET field1 = 'updated value' WHERE field2 = 'N'")] + (is (= :update (:type info))) + (is (= false (:returning? info))) + (is (= #{:example} (:tables info))))) + +(deftest delete-query-analyzed-correctly + (let [info (query-info "DELETE FROM example WHERE field2 = 'N'")] + (is (= :delete (:type info))) + (is (= false (:returning? info))) + (is (= #{:example} (:tables info))))) + +(deftest unsupported-query-types-throw-exception + (is (thrown? Exception (query-info "ALTER TABLE example ADD column4 NUMBER(3) NOT NULL")))) + +(deftest multiple-query-tables-are-included-correctly + (let [info (query-info "SELECT * FROM book JOIN publisher ON book.publisher_id = publisher.id WHERE price > 100.00 ORDER BY title")] + (is (= :select (:type info))) + (is (= true (:returning? info))) + (is (= #{:book :publisher} (:tables info))))) diff --git a/test/views/sql/test_fixtures.clj b/test/views/sql/test_fixtures.clj new file mode 100644 index 0000000..4f9d3b5 --- /dev/null +++ b/test/views/sql/test_fixtures.clj @@ -0,0 +1,16 @@ +(ns views.sql.test-fixtures + (:use + clojure.test + views.sql.core)) + +(def test-db {:not-a-real-db-conn true}) + +(def test-view-system (atom {})) + +(defn clear-query-cache-fixture [f] + (reset! query-info-cache {}) + (f)) + +(defn reset-test-view-system-fixture [f] + (reset! test-view-system {}) + (f)) diff --git a/test/views/sql/vexec_tests.clj b/test/views/sql/vexec_tests.clj new file mode 100644 index 0000000..1cffc1e --- /dev/null +++ b/test/views/sql/vexec_tests.clj @@ -0,0 +1,78 @@ +(ns views.sql.vexec-tests + (:use + clojure.test + views.sql.test-fixtures + views.sql.core) + (:require + [clojure.java.jdbc :as jdbc] + [views.core :as views])) + +(def redefs-called (atom {})) + +(defn ->redef-fn + [name & [return-value]] + (fn [& args] + (swap! redefs-called assoc name (vec args)) + return-value)) + +(defn vexec-redefs-fixture [f] + (reset! redefs-called {}) + (with-redefs + [jdbc/query (->redef-fn :jdbc/query :jdbc/query-return-value) + jdbc/execute! (->redef-fn :jdbc/execute! :jdbc/execute!-return-value) + views/put-hints! (->redef-fn :views/put-hints!)] + (f))) + + +(use-fixtures :each clear-query-cache-fixture reset-test-view-system-fixture vexec-redefs-fixture) + +(deftest vexec-runs-query-and-puts-hints + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL)"] + result (vexec! test-view-system test-db sqlvec)] + (is (= [test-db sqlvec] (:jdbc/execute! @redefs-called))) + (is (= :jdbc/execute!-return-value result)) + (is (= [test-view-system [(views/hint nil #{:example} hint-type)]] + (:views/put-hints! @redefs-called))))) + +(deftest vexec-runs-query-with-returning-clause + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL) RETURNING *"] + result (vexec! test-view-system test-db sqlvec)] + (is (= [test-db sqlvec] (:jdbc/query @redefs-called))) + (is (= :jdbc/query-return-value result)) + (is (= [test-view-system [(views/hint nil #{:example} hint-type)]] + (:views/put-hints! @redefs-called))))) + +(deftest namespace-is-passed-along-to-hints-via-vexec + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL)"] + result (vexec! test-view-system test-db sqlvec {:namespace :foobar})] + (is (= [test-db sqlvec] (:jdbc/execute! @redefs-called))) + (is (= :jdbc/execute!-return-value result)) + (is (= [test-view-system [(views/hint :foobar #{:example} hint-type)]] + (:views/put-hints! @redefs-called))))) + +(deftest manually-provided-hints-to-vexec-are-passed-to-views-system + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL)"] + result (vexec! test-view-system test-db sqlvec [:foo :bar])] + (is (= [test-db sqlvec] (:jdbc/execute! @redefs-called))) + (is (= :jdbc/execute!-return-value result)) + (is (= [test-view-system [(views/hint nil #{:foo :bar} hint-type)]] + (:views/put-hints! @redefs-called))))) + +(deftest manually-provided-hints-to-vexec-also-requires-specifying-returning-option + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL) RETURNING *"] + result (vexec! test-view-system test-db sqlvec [:foo :bar])] + ; means jdbc/execute! was called which is correct behaviour since the returning option was not specified. + ; if jdbc/execute! was really called it would throw an exception since the INSERT query used has a RETURNING clause + (is (= [test-db sqlvec] (:jdbc/execute! @redefs-called))) + (is (= :jdbc/execute!-return-value result)) + (is (= [test-view-system [(views/hint nil #{:foo :bar} hint-type)]] + (:views/put-hints! @redefs-called)))) + ; manually reset some things + (reset! redefs-called {}) + (reset! query-info-cache {}) + (let [sqlvec ["INSERT INTO example (field1, field2, field3) VALUES ('test', 'N', NULL) RETURNING *"] + result (vexec! test-view-system test-db sqlvec [:foo :bar] {:returning? true})] + (is (= [test-db sqlvec] (:jdbc/query @redefs-called))) + (is (= :jdbc/query-return-value result)) + (is (= [test-view-system [(views/hint nil #{:foo :bar} hint-type)]] + (:views/put-hints! @redefs-called))))) diff --git a/test/views/sql/view_test.clj b/test/views/sql/view_test.clj new file mode 100644 index 0000000..102cdab --- /dev/null +++ b/test/views/sql/view_test.clj @@ -0,0 +1,96 @@ +(ns views.sql.view-test + (:import (views.protocols IView)) + (:use + clojure.test + views.sql.test-fixtures + views.sql.core + views.sql.view + views.protocols) + (:require + [clojure.java.jdbc :as jdbc] + [views.core :as views])) + +(def redefs-called (atom {})) + +(defn ->redef-fn + [name & [return-value]] + (fn [& args] + (swap! redefs-called assoc name (vec args)) + return-value)) + +(defn vexec-redefs-fixture [f] + (reset! redefs-called {}) + (with-redefs + [jdbc/query (->redef-fn :jdbc/query :jdbc/query-return-value) + views/put-hints! (->redef-fn :views/put-hints!)] + (f))) + + +(use-fixtures :each clear-query-cache-fixture reset-test-view-system-fixture vexec-redefs-fixture) + +(deftest basic-sql-view-works + (let [sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [] sqlvec) + sql-view (view :test-view test-db sql-fn)] + (is (satisfies? IView sql-view)) + (is (= :test-view (id sql-view))) + (is (= true (relevant? sql-view nil [] [(views/hint nil #{:foobar} hint-type)]))) + (is (= false (relevant? sql-view nil [] [(views/hint nil #{:baz} hint-type)]))) + (is (= :jdbc/query-return-value (data sql-view nil []))) + (is (= [test-db sqlvec {}] + (:jdbc/query @redefs-called))))) + +(deftest basic-sql-view-works-with-parameters + (let [sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [a b] (into sqlvec [a b])) + sql-view (view :test-view test-db sql-fn)] + (is (= true (relevant? sql-view nil [1 2] [(views/hint nil #{:foobar} hint-type)]))) + (is (= :jdbc/query-return-value (data sql-view nil [1 2]))) + (is (= [test-db (into sqlvec [1 2]) {}] + (:jdbc/query @redefs-called))))) + +(deftest basic-sql-view-works-with-namespace + (let [sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [] sqlvec) + sql-view (view :test-view test-db sql-fn)] + (is (= true (relevant? sql-view :abc [] [(views/hint :abc #{:foobar} hint-type)]))) + (is (= false (relevant? sql-view :123 [] [(views/hint :abc #{:foobar} hint-type)]))) + (is (= :jdbc/query-return-value (data sql-view nil []))) + (is (= [test-db sqlvec {}] + (:jdbc/query @redefs-called))))) + +(deftest view-db-fn-is-used-when-provided + (let [alt-test-db {:alternate-test-db-conn true} + db-fn (fn [namespace] alt-test-db) + sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [] sqlvec) + sql-view (view :test-view db-fn sql-fn)] + (is (= :jdbc/query-return-value (data sql-view nil []))) + (is (= [alt-test-db sqlvec {}] + (:jdbc/query @redefs-called))))) + +(deftest view-db-fn-is-passed-namespace + (let [test-namespace :test-namespace + alt-test-db {:alternate-test-db-conn true} + db-fn (fn [namespace] + (is (= namespace :test-namespace)) + alt-test-db) + sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [] sqlvec) + sql-view (view :test-view db-fn sql-fn)] + (is (= :jdbc/query-return-value (data sql-view test-namespace []))) + (is (= [alt-test-db sqlvec {}] + (:jdbc/query @redefs-called))))) + +(deftest manually-specified-view-hints-are-used-correctly + (with-redefs + [query-tables (->redef-fn :query-tables)] + (let [sqlvec ["SELECT * FROM foobar"] + sql-fn (fn [] sqlvec) + sql-view (view :test-view test-db sql-fn [:foo :bar])] + (is (= false (relevant? sql-view nil [] [(views/hint nil #{:foobar} hint-type)]))) + (is (= true (relevant? sql-view nil [] [(views/hint nil #{:foo} hint-type)]))) + (is (= true (relevant? sql-view nil [] [(views/hint nil #{:bar} hint-type)]))) + (is (= true (relevant? sql-view nil [] [(views/hint nil #{:bar} hint-type) + (views/hint nil #{:foo} hint-type)]))) + (is (not (contains? @redefs-called :query-tables))))))