mongo-driver-3/test/mongo_driver_3/collection_test.clj
2019-11-14 10:01:38 +08:00

462 lines
No EOL
18 KiB
Clojure

(ns mongo-driver-3.collection-test
(:require [clojure.test :refer :all]
[mongo-driver-3.client :as mg]
[mongo-driver-3.collection :as mc])
(:import (com.mongodb ReadConcern ReadPreference WriteConcern)
(java.util.concurrent TimeUnit)
(com.mongodb.client.model InsertOneOptions InsertManyOptions DeleteOptions FindOneAndUpdateOptions ReturnDocument FindOneAndReplaceOptions CountOptions UpdateOptions ReplaceOptions IndexOptions CreateCollectionOptions)
(java.time ZoneId LocalDate LocalTime LocalDateTime)
(java.util Date UUID)))
;;; Unit
(deftest test->ReadConcern
(is (nil? (mc/->ReadConcern nil)))
(is (thrown? IllegalArgumentException (mc/->ReadConcern "invalid")))
(is (instance? ReadConcern (mc/->ReadConcern :available))))
(deftest test->ReadPreference
(is (nil? (mc/->ReadPreference nil)))
(is (thrown? IllegalArgumentException (mc/->ReadPreference "invalid")))
(is (instance? ReadPreference (mc/->ReadPreference :primary))))
(deftest test->WriteConcern
(is (= (WriteConcern/W1) (mc/->WriteConcern {:write-concern :w1})) "accepts kw")
(is (= (WriteConcern/W1) (mc/->WriteConcern {:write-concern (WriteConcern/W1)})) "accepts WriteConcern")
(is (= (WriteConcern/ACKNOWLEDGED) (mc/->WriteConcern {:write-concern "invalid"})) "defaults to acknowledged")
(is (= 1 (.getW (mc/->WriteConcern {:write-concern/w 1}))) "set w")
(is (= 2 (.getW (mc/->WriteConcern {:write-concern (WriteConcern/W2)}))))
(is (= 1 (.getW (mc/->WriteConcern {:write-concern (WriteConcern/W2) :write-concern/w 1}))) "prefer granular option")
(is (true? (.getJournal (mc/->WriteConcern {:write-concern/journal? true}))) "can set journal")
(is (= 77 (.getWTimeout (mc/->WriteConcern {:write-concern/w-timeout-ms 77}) (TimeUnit/MILLISECONDS))) "can set timeout"))
(deftest test->InsertOneOptions
(is (instance? InsertOneOptions (mc/->InsertOneOptions {})))
(is (true? (.getBypassDocumentValidation (mc/->InsertOneOptions {:bypass-document-validation? true}))))
(is (true? (.getBypassDocumentValidation (mc/->InsertOneOptions
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)})))
"configure directly")
(is (false? (.getBypassDocumentValidation (mc/->InsertOneOptions
{:insert-one-options (.bypassDocumentValidation (InsertOneOptions.) true)
:bypass-document-validation? false})))
"can override"))
(deftest test->ReplaceOptions
(is (instance? ReplaceOptions (mc/->ReplaceOptions {})))
(are [expected arg]
(= expected (.isUpsert (mc/->ReplaceOptions {:upsert? arg})))
true true
false false
false nil) (is (true? (.getBypassDocumentValidation (mc/->ReplaceOptions {:bypass-document-validation? true}))))
(is (true? (.getBypassDocumentValidation (mc/->ReplaceOptions
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)})))
"configure directly")
(is (false? (.getBypassDocumentValidation (mc/->ReplaceOptions
{:replace-options (.bypassDocumentValidation (ReplaceOptions.) true)
:bypass-document-validation? false})))
"can override"))
(deftest test->UpdateOptions
(is (instance? UpdateOptions (mc/->UpdateOptions {})))
(are [expected arg]
(= expected (.isUpsert (mc/->UpdateOptions {:upsert? arg})))
true true
false false
false nil)
(is (true? (.getBypassDocumentValidation (mc/->UpdateOptions {:bypass-document-validation? true}))))
(is (true? (.getBypassDocumentValidation (mc/->UpdateOptions
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)})))
"configure directly")
(is (false? (.getBypassDocumentValidation (mc/->UpdateOptions
{:update-options (.bypassDocumentValidation (UpdateOptions.) true)
:bypass-document-validation? false})))
"can override"))
(deftest test->InsertManyOptions
(is (instance? InsertManyOptions (mc/->InsertManyOptions {})))
(are [expected arg]
(= expected (.getBypassDocumentValidation (mc/->InsertManyOptions {:bypass-document-validation? arg})))
true true
false false
nil nil)
(are [expected arg]
(= expected (.isOrdered (mc/->InsertManyOptions {:ordered? arg})))
true true
false false
true nil)
(is (true? (.getBypassDocumentValidation (mc/->InsertManyOptions
{:insert-many-options (.bypassDocumentValidation (InsertManyOptions.) true)})))
"configure directly")
(is (false? (.getBypassDocumentValidation (mc/->InsertManyOptions
{:insert-one-options (.bypassDocumentValidation (InsertManyOptions.) true)
:bypass-document-validation? false})))
"can override"))
(deftest test->DeleteOptions
(is (instance? DeleteOptions (mc/->DeleteOptions {})))
(let [opts (DeleteOptions.)]
(is (= opts (mc/->DeleteOptions {:delete-options opts})) "configure directly")))
(deftest test->FindOneAndUpdateOptions
(is (instance? FindOneAndUpdateOptions (mc/->FindOneAndUpdateOptions {})))
(let [opts (FindOneAndUpdateOptions.)]
(is (= opts (mc/->FindOneAndUpdateOptions {:find-one-and-update-options opts})) "configure directly"))
(are [expected arg]
(= expected (.isUpsert (mc/->FindOneAndUpdateOptions {:upsert? arg})))
true true
false false
false nil)
(are [expected arg]
(= expected (.getReturnDocument (mc/->FindOneAndUpdateOptions {:return-new? arg})))
(ReturnDocument/AFTER) true
(ReturnDocument/BEFORE) false
(ReturnDocument/BEFORE) nil)
(is (= {"_id" 1} (.getSort (mc/->FindOneAndUpdateOptions {:sort {:_id 1}}))))
(is (= {"_id" 1} (.getProjection (mc/->FindOneAndUpdateOptions {:projection {:_id 1}})))))
(deftest test->FindOneAndReplaceOptions
(is (instance? FindOneAndReplaceOptions (mc/->FindOneAndReplaceOptions {})))
(let [opts (FindOneAndReplaceOptions.)]
(is (= opts (mc/->FindOneAndReplaceOptions {:find-one-and-replace-options opts})) "configure directly"))
(are [expected arg]
(= expected (.isUpsert (mc/->FindOneAndReplaceOptions {:upsert? arg})))
true true
false false
false nil)
(are [expected arg]
(= expected (.getReturnDocument (mc/->FindOneAndReplaceOptions {:return-new? arg})))
(ReturnDocument/AFTER) true
(ReturnDocument/BEFORE) false
(ReturnDocument/BEFORE) nil)
(is (= {"_id" 1} (.getSort (mc/->FindOneAndReplaceOptions {:sort {:_id 1}}))))
(is (= {"_id" 1} (.getProjection (mc/->FindOneAndReplaceOptions {:projection {:_id 1}})))))
(deftest test->CountOptions
(is (instance? CountOptions (mc/->CountOptions {})))
(let [opts (CountOptions.)]
(is (= opts (mc/->CountOptions {:count-options opts})) "configure directly"))
(is (= {"a" 1} (.getHint (mc/->CountOptions {:hint {:a 1}}))))
(is (= 7 (.getLimit (mc/->CountOptions {:limit 7}))))
(is (= 2 (.getSkip (mc/->CountOptions {:skip 2}))))
(is (= 42 (.getMaxTime (mc/->CountOptions {:max-time-ms 42}) (TimeUnit/MILLISECONDS)))))
(deftest test->IndexOptions
(is (instance? IndexOptions (mc/->IndexOptions {})))
(are [expected arg]
(= expected (.isSparse (mc/->IndexOptions {:sparse? arg})))
true true
false false
false nil)
(are [expected arg]
(= expected (.isUnique (mc/->IndexOptions {:unique? arg})))
true true
false false
false nil)
(let [opts (IndexOptions.)]
(is (= opts (mc/->IndexOptions {:index-options opts})) "configure directly")))
(deftest test->CreateCollectionOptions
(are [expected arg]
(= expected (.isCapped (mc/->CreateCollectionOptions {:capped? arg})))
true true
false false
false nil)
(is (= 7 (.getMaxDocuments (mc/->CreateCollectionOptions {:max-documents 7}))))
(is (= 42 (.getSizeInBytes (mc/->CreateCollectionOptions {:max-size-bytes 42}))))
(let [opts (-> (CreateCollectionOptions.) (.maxDocuments 5))]
(is (= opts (mc/->CreateCollectionOptions {:create-collection-options opts})) "configure directly")
(is (= 5 (.getMaxDocuments (mc/->CreateCollectionOptions {:create-collection-options opts}))))
(is (= 7 (.getMaxDocuments (mc/->CreateCollectionOptions {:create-collection-options opts :max-documents 7})))
"can override")))
;;; Integration
; docker run -it --rm -p 27017:27017 mongo:4.2
(def client (atom nil))
(defn- setup-connections [f]
(reset! client (mg/create))
(f)
(mg/close @client))
(use-fixtures :once setup-connections)
(defn new-db
[client]
(mg/get-db client (.toString (UUID/randomUUID))))
(deftest ^:integration test-insert-one
(testing "basic insertion"
(let [db (new-db @client)
doc {:hello "world"}
_ (mc/insert-one db "test" doc)
res (mc/find-maps db "test" {})]
(is (= 1 (count res)))
(is (= doc (select-keys (first res) [:hello])))))
(testing "conversion round trip"
(let [db (new-db @client)
doc {:nil nil
:string "string"
:int 1
:float 1.1
:ratio 1/2
:list ["moo" "cow"]
:set #{1 2}
:map {:moo "cow"}
:kw :keyword
:bool true
:date (Date.)
:localdate (LocalDate/now)
:localdatetime (LocalDateTime/now)
:localtime (LocalTime/now)}
_ (mc/insert-one db "test" doc)
res (mc/find-one-as-map db "test" {} {:projection {:_id 0}})]
(is (= {:nil nil
:string "string"
:int 1
:float 1.1
:ratio 0.5
:list ["moo" "cow"]
:set [1 2]
:map {:moo "cow"}
:kw "keyword"
:bool true
:date (:date doc)
:localdate (Date/from (.toInstant (.atStartOfDay (:localdate doc) (ZoneId/of "UTC"))))
:localdatetime (Date/from (.toInstant (.atZone (:localdatetime doc) (ZoneId/of "UTC"))))
:localtime (Date/from (.toInstant (.atZone (LocalDateTime/of (LocalDate/EPOCH) (:localtime doc)) (ZoneId/of "UTC"))))} res)))))
(deftest ^:integration test-insert-many
(testing "basic insertions"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1} {:id 2}])
res (mc/find-maps db "test" {})]
(is (= 2 (count res)))
(is (= [1 2] (map :id res)))))
(testing "no docs"
(let [db (new-db @client)]
(is (thrown? IllegalArgumentException (mc/insert-many db "test" [])))
(is (thrown? IllegalArgumentException (mc/insert-many db "test" nil))))))
(deftest ^:integration test-delete-one
(testing "not exist"
(let [db (new-db @client)
res (mc/delete-one db "test" {:id :a})]
(is (= 0 (.getDeletedCount res)))))
(testing "one at a time"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:v 1} {:v 1}])]
(is (= 1 (.getDeletedCount (mc/delete-one db "test" {:v 1}))))
(is (= 1 (.getDeletedCount (mc/delete-one db "test" {:v 1}))))
(is (= 0 (.getDeletedCount (mc/delete-one db "test" {:v 1})))))))
(deftest ^:integration test-delete-many
(testing "not exist"
(let [db (new-db @client)
res (mc/delete-many db "test" {:id :a})]
(is (= 0 (.getDeletedCount res)))))
(testing "all together"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:v 1} {:v 1}])]
(is (= 2 (.getDeletedCount (mc/delete-many db "test" {:v 1}))))
(is (= 0 (.getDeletedCount (mc/delete-many db "test" {:v 1})))))))
(deftest ^:integration test-find-maps
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :a 1 :v 2} {:id 2 :a 1 :v 3} {:id 3 :v 1}])]
(testing "query"
(are [ids q] (= ids (map :id (mc/find-maps db "test" q)))
[1 2 3] {}
[1] {:id 1}
[1 2] {:a {:$exists true}}
[2] {:v {:$gt 2}}))
(testing "sort"
(are [ids s] (= ids (map :id (mc/find-maps db "test" {} {:sort s})))
[1 2 3] {}
[3 1 2] {:v 1}
[2 1 3] {:v -1}))
(testing "limit"
(are [cnt n] (= cnt (count (mc/find-maps db "test" {} {:limit n})))
1 1
2 2
3 3
3 4))
(testing "projection"
(are [ks p] (= ks (keys (first (mc/find-maps db "test" {} {:projection p}))))
[:_id :id :a :v] {}
[:_id :a] {:a 1}
[:id :a :v] {:_id 0}))))
(deftest ^:integration test-find-one-as-map
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :a 1 :v 2} {:id 2 :a 1 :v 3} {:id 3 :v 1}])]
(testing "query"
(are [id q] (= id (:id (mc/find-one-as-map db "test" q)))
1 {}
2 {:id 2}
1 {:a {:$exists true}}
2 {:v {:$gt 2}}))
(testing "sort"
(are [id s] (= id (:id (mc/find-one-as-map db "test" {} {:sort s})))
1 {}
3 {:v 1}
2 {:v -1}))
(testing "projection"
(are [ks p] (= ks (keys (mc/find-one-as-map db "test" {} {:projection p})))
[:_id :id :a :v] {}
[:_id :a] {:a 1}
[:id :a :v] {:_id 0}))))
(deftest ^:integration test-count-documents
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :a 1 :v 2} {:id 2 :a 1 :v 3} {:id 3 :v 1}])]
(testing "all"
(is (= 3 (mc/count-documents db "test"))))
(testing "query"
(are [id q] (= id (mc/count-documents db "test" q))
3 {}
1 {:id 2}
2 {:a {:$exists true}}
1 {:v {:$gt 2}}))
(testing "skip"
(are [id s] (= id (mc/count-documents db "test" {} {:skip s}))
3 nil
3 0
2 1
1 2
0 3
0 4))
(testing "limit"
(are [id s] (= id (mc/count-documents db "test" {} {:limit s}))
3 nil
3 0
1 1
2 2
3 3
3 4))))
(deftest ^:integration test-find-one-and-update
(testing "return new"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :v 1} {:id 2 :v 1}])]
(is (= {:id 1 :v 1} (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1} :$inc {:v 1}}) :_id)))
(is (= {:id 2 :v 2 :r 1} (dissoc (mc/find-one-and-update db "test" {:id 2} {:$set {:r 1} :$inc {:v 1}} {:return-new? true}) :_id)))))
(testing "upsert"
(let [db (new-db @client)]
(is (nil? (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1}} {:return-new? true}) :_id)))
(is (= {:id 1 :r 1} (dissoc (mc/find-one-and-update db "test" {:id 1} {:$set {:r 1}} {:return-new? true :upsert? true}) :_id))))))
(deftest ^:integration test-find-one-and-replace
(testing "return new"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :v 1} {:id 2 :v 1}])]
(is (= {:id 1 :v 1} (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2}) :_id)))
(is (= {:id 2 :v 2} (dissoc (mc/find-one-and-replace db "test" {:id 2} {:id 2 :v 2} {:return-new? true}) :_id)))))
(testing "upsert"
(let [db (new-db @client)]
(is (nil? (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2} {:return-new? true}) :_id)))
(is (= {:id 1 :v 2} (dissoc (mc/find-one-and-replace db "test" {:id 1} {:id 1 :v 2} {:return-new? true :upsert? true}) :_id))))))
(deftest ^:integration test-replace-one
(testing "existing doc"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :v 1} {:id 2 :v 1}])
r1 (mc/replace-one db "test" {:v 1} {:v 2} {})
r2 (mc/replace-one db "test" {:v 1} {:v 2} {})
r3 (mc/replace-one db "test" {:v 1} {:v 2} {})]
;; replace-one will match at most 1
(is (= 1 (.getMatchedCount r1)))
(is (= 1 (.getModifiedCount r1)))
(is (= 1 (.getMatchedCount r2)))
(is (= 1 (.getModifiedCount r2)))
(is (= 0 (.getMatchedCount r3)))
(is (= 0 (.getModifiedCount r3)))))
(testing "upsert"
(let [db (new-db @client)]
(let [res (mc/replace-one db "test" {:id 1} {:v 2} {})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (nil? (.getUpsertedId res))))
(let [res (mc/replace-one db "test" {:id 1} {:v 2} {:upsert? true})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (some? (.getUpsertedId res)))))))
(deftest ^:integration test-update-one
(testing "existing doc"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :v 1} {:id 2 :v 1}])
r1 (mc/update-one db "test" {:v 1} {:$set {:v 2}} {})
r2 (mc/update-one db "test" {:v 1} {:$set {:v 2}} {})
r3 (mc/update-one db "test" {:v 1} {:$set {:v 2}} {})]
;; update-one will match at most 1
(is (= 1 (.getMatchedCount r1)))
(is (= 1 (.getModifiedCount r1)))
(is (= 1 (.getMatchedCount r2)))
(is (= 1 (.getModifiedCount r2)))
(is (= 0 (.getMatchedCount r3)))
(is (= 0 (.getModifiedCount r3)))))
(testing "upsert"
(let [db (new-db @client)]
(let [res (mc/update-one db "test" {:id 1} {:$set {:r 1}} {})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (nil? (.getUpsertedId res))))
(let [res (mc/update-one db "test" {:id 1} {:$set {:r 1}} {:upsert? true})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (some? (.getUpsertedId res)))))))
(deftest ^:integration test-update-many
(testing "existing doc"
(let [db (new-db @client)
_ (mc/insert-many db "test" [{:id 1 :v 1} {:id 2 :v 1}])
r1 (mc/update-many db "test" {:v 1} {:$set {:v 2}} {})
r2 (mc/update-many db "test" {:v 1} {:$set {:v 2}} {})]
(is (= 2 (.getMatchedCount r1)))
(is (= 2 (.getModifiedCount r1)))
(is (= 0 (.getMatchedCount r2)))
(is (= 0 (.getModifiedCount r2)))))
(testing "upsert"
(let [db (new-db @client)]
(let [res (mc/update-many db "test" {:id 1} {:$set {:r 1}} {})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (nil? (.getUpsertedId res))))
(let [res (mc/update-many db "test" {:id 1} {:$set {:r 1}} {:upsert? true})]
(is (= 0 (.getMatchedCount res)))
(is (= 0 (.getModifiedCount res)))
(is (some? (.getUpsertedId res)))))))