babashka/test-resources/lib_tests/pyramid/core_test.cljc
Gabriel Horner 744c5d0732 Add some additional libraries to test
Also improved docs around add-libtest
Close #1137
Close #1128
2022-01-11 23:39:10 -05:00

574 lines
22 KiB
Clojure

(ns pyramid.core-test
(:require
[pyramid.core :as p]
[pyramid.ident :as ident]
[clojure.test :as t]))
(t/deftest normalization
(t/is (= {:person/id {0 {:person/id 0}}}
(p/db [{:person/id 0}]))
"a single entity")
(t/is (= {:person/id {0 {:person/id 0
:person/name "asdf"}
1 {:person/id 1
:person/name "jkl"}}}
(p/db [{:person/id 0
:person/name "asdf"}
{:person/id 1
:person/name "jkl"}]))
"multiple entities with attributes")
(t/is (= {:person/id {0 {:person/id 0
:person/name "asdf"}
1 {:person/id 1
:person/name "jkl"}}
:people [[:person/id 0]
[:person/id 1]]}
(p/db [{:people [{:person/id 0
:person/name "asdf"}
{:person/id 1
:person/name "jkl"}]}]))
"nested under a key")
(t/is (= {:person/id {0 {:person/id 0
:some-data {1 "hello"
3 "world"}}}}
(p/db [{:person/id 0
:some-data {1 "hello"
3 "world"}}]))
"Map with numbers as keys")
(t/is (= {:a/id {1 {:a/id 1
:b [{:c [:d/id 1]}]}}
:d/id {1 {:d/id 1
:d/txt "a"}}}
(p/db [{:a/id 1
:b [{:c {:d/id 1
:d/txt "a"}}]}]))
"Collections of non-entities still get normalized")
(t/is (= {:person/id
{123
{:person/id 123,
:person/name "Will",
:contact {:phone "000-000-0001"},
:best-friend [:person/id 456],
:friends
[[:person/id 9001]
[:person/id 456]
[:person/id 789]
[:person/id 1000]]},
456
{:person/id 456,
:person/name "Jose",
:account/email "asdf@jkl",
:best-friend [:person/id 123]},
9001 #:person{:id 9001, :name "Georgia"},
789 #:person{:id 789, :name "Frank"},
1000 #:person{:id 1000, :name "Robert"}}}
(p/db [{:person/id 123
:person/name "Will"
:contact {:phone "000-000-0001"}
:best-friend
{:person/id 456
:person/name "Jose"
:account/email "asdf@jkl"}
:friends
[{:person/id 9001
:person/name "Georgia"}
{:person/id 456
:person/name "Jose"}
{:person/id 789
:person/name "Frank"}
{:person/id 1000
:person/name "Robert"}]}
{:person/id 456
:best-friend {:person/id 123}}]))
"refs"))
(t/deftest non-entities
(t/is (= {:foo ["bar"]} (p/db [{:foo ["bar"]}])))
(t/is (= {:person/id {0 {:person/id 0
:foo ["bar"]}}}
(p/db [{:person/id 0
:foo ["bar"]}]))))
(t/deftest custom-schema
(t/is (= {:color {"red" {:color "red" :hex "#ff0000"}}}
(p/db [{:color "red" :hex "#ff0000"}]
(ident/by-keys :color)))
"ident/by-keys")
(t/is (= {:color {"red" {:color "red" :hex "#ff0000"}}}
(p/db [^{:db/ident :color}
{:color "red" :hex "#ff0000"}]))
"local schema")
(t/testing "complex schema"
(let [db (p/db [{:type "person"
:id "1234"
:purchases [{:type "item"
:id "1234"}]}
{:type "item"
:id "5678"}
{:type "foo"}
{:id "bar"}]
(fn [entity]
(let [{:keys [type id]} entity]
(when (and (some? type) (some? id))
[(keyword type "id") id]))))]
(t/is (= {:person/id
{"1234" {:type "person", :id "1234", :purchases [[:item/id "1234"]]}},
:item/id
{"1234" {:type "item", :id "1234"}, "5678" {:type "item", :id "5678"}},
:type "foo",
:id "bar"}
db)
"correctly identifies entities")
(t/is (= {[:person/id "1234"]
{:type "person", :id "1234", :purchases [{:type "item", :id "1234"}]}}
(p/pull db [{[:person/id "1234"] [:type :id {:purchases [:type :id]}]}]))
"pull"))))
(t/deftest add
(t/is (= {:person/id {0 {:person/id 0}}}
(p/add {} {:person/id 0})))
(t/is (= {:person/id {0 {:person/id 0 :person/name "Gill"}
1 {:person/id 1}}}
(p/add
{}
{:person/id 0}
{:person/id 1}
{:person/id 0 :person/name "Gill"}))))
(t/deftest add-report
(t/is (= {:db {:person/id {0 {:person/id 0}}}
:entities #{[:person/id 0]}}
(p/add-report {} {:person/id 0})))
(t/is (= {:db {:person/id {0 {:person/id 0
:person/name "Gill"
:best-friend [:person/id 1]}
1 {:person/id 1
:person/name "Uma"}}
:me [:person/id 0]}
:entities #{[:person/id 0]
[:person/id 1]}}
(p/add-report {} {:me {:person/id 0
:person/name "Gill"
:best-friend {:person/id 1
:person/name "Uma"}}})))
#_(t/is (= {:db {:person/id {0 {:person/id 0 :person/name "Gill"}
1 {:person/id 1}}}
:entities #{{:person/id 0 :person/name "Gill"}
{:person/id 1}}}
(p/add-report
{}
{:person/id 0}
{:person/id 1}
{:person/id 0 :person/name "Gill"}))))
(def data
{:people/all [{:person/id 0
:person/name "Alice"
:person/age 25
:best-friend {:person/id 1}
:person/favorites
{:favorite/ice-cream "vanilla"}}
{:person/id 1
:person/name "Bob"
:person/age 23}]})
(def db
(p/db [data]))
(t/deftest pull
(t/is (= #:people{:all [{:person/id 0} {:person/id 1}]}
(p/pull db [:people/all]))
"simple key")
(t/is (= {:people/all [{:person/name "Alice"
:person/id 0}
{:person/name "Bob"
:person/id 1}]}
(p/pull db [{:people/all [:person/name :person/id]}]))
"basic join + prop")
(t/is (= #:people{:all [{:person/name "Alice"
:person/id 0
:best-friend #:person{:name "Bob", :id 1 :age 23}}
#:person{:name "Bob", :id 1}]}
(p/pull db [#:people{:all [:person/name :person/id :best-friend]}]))
"join + prop + join ref lookup")
(t/is (= #:people{:all [{:person/name "Alice"
:person/id 0
:best-friend #:person{:name "Bob"}}
#:person{:name "Bob", :id 1}]}
(p/pull db [#:people{:all [:person/name
:person/id
{:best-friend [:person/name]}]}]))
"join + prop, ref as prop resolver")
(t/is (= {[:person/id 1] #:person{:id 1, :name "Bob", :age 23}}
(p/pull db [[:person/id 1]]))
"ident acts as ref lookup")
(t/is (= {[:person/id 0] {:person/id 0
:person/name "Alice"
:person/age 25
:best-friend {:person/id 1}
:person/favorites #:favorite{:ice-cream "vanilla"}}}
(p/pull db [[:person/id 0]]))
"ident does not resolve nested refs")
(t/is (= {[:person/id 0] #:person{:id 0
:name "Alice"
:favorites #:favorite{:ice-cream "vanilla"}}}
(p/pull db [{[:person/id 0] [:person/id
:person/name
:person/favorites]}]))
"join on ident")
(t/is (= {:people/all [{:person/name "Alice"
:person/id 0
:best-friend #:person{:name "Bob", :id 1 :age 23}}
#:person{:name "Bob", :id 1}]
[:person/id 1] #:person{:age 23}}
(p/pull db [{:people/all [:person/name :person/id :best-friend]}
{[:person/id 1] [:person/age]}]))
"multiple joins")
(t/testing "includes params"
(t/is (= #:people{:all [#:person{:name "Bob", :id 1}]}
(p/pull (-> db
(p/add {'(:people/all {:with "params"}) [[:person/id 1]]}))
'[{(:people/all {:with "params"})
[:person/name :person/id]}])))
(t/is (= '{:person/foo {:person/id 1
:person/name "Bob"}}
(p/pull (-> db
(p/add {'(:person/foo {:person/id 2})
{:person/id 1}}))
'[{(:person/foo {:person/id 2})
[:person/name :person/id]}]))
"params that include an entity-looking thing should not be normalized")
(t/is (= {}
(p/pull db '[([:person/id 1] {:with "params"})])))
(t/is (= {}
(p/pull db '[{(:people/all {:with "params"})
[:person/name :person/id]}]))))
(t/testing "union"
(let [data {:chat/entries
[{:message/id 0
:message/text "foo"
:chat.entry/timestamp "1234"}
{:message/id 1
:message/text "bar"
:chat.entry/timestamp "1235"}
{:audio/id 0
:audio/url "audio://asdf.jkl"
:audio/duration 1234
:chat.entry/timestamp "4567"}
{:photo/id 0
:photo/url "photo://asdf_10x10.jkl"
:photo/height 10
:photo/width 10
:chat.entry/timestamp "7890"}]}
db1 (p/db [data])
query [{:chat/entries
{:message/id
[:message/id :message/text :chat.entry/timestamp]
:audio/id
[:audio/id :audio/url :audio/duration :chat.entry/timestamp]
:photo/id
[:photo/id :photo/url :photo/width :photo/height :chat.entry/timestamp]
:asdf/jkl [:asdf/jkl]}}]]
(t/is (= #:chat{:entries [{:message/id 0
:message/text "foo"
:chat.entry/timestamp "1234"}
{:message/id 1
:message/text "bar"
:chat.entry/timestamp "1235"}
{:audio/id 0
:audio/url "audio://asdf.jkl"
:audio/duration 1234
:chat.entry/timestamp "4567"}
{:photo/id 0
:photo/url "photo://asdf_10x10.jkl"
:photo/width 10
:photo/height 10
:chat.entry/timestamp "7890"}]}
(p/pull db1 query)))))
(t/testing "not found"
(t/is (= {} (p/pull {} [:foo])))
(t/is (= {} (p/pull {} [:foo :bar :baz])))
(t/is (= {} (p/pull {} [:foo {:bar [:asdf]} :baz])))
(t/is (= {:foo "bar"}
(p/pull {:foo "bar"} [:foo {:bar [:asdf]} :baz])))
(t/is (= {:bar {:asdf 123}}
(p/pull
{:bar {:asdf 123}}
[:foo {:bar [:asdf :jkl]} :baz])))
(t/is (= {:bar {}}
(p/pull
(p/db [{:bar {:bar/id 0}}
{:bar/id 0
:qwerty 1234}])
[:foo {:bar [:asdf :jkl]} :baz])))
(t/is (= {:bar {:asdf "jkl"}}
(p/pull
(p/db [{:bar {:bar/id 0}}
{:bar/id 0
:asdf "jkl"}])
[:foo {:bar [:asdf :jkl]} :baz])))
(t/is (= {:bar {}}
(p/pull
(p/db [{:bar {:bar/id 0}}
{:bar/id 1
:asdf "jkl"}])
[:foo {:bar [:asdf :jkl]} :baz])))
(t/is (= {:foo [{:bar/id 1
:bar/name "asdf"}
{:baz/id 1
:baz/name "jkl"}]}
(p/pull
(p/db [{:foo [{:bar/id 1
:bar/name "asdf"}
{:baz/id 1
:baz/name "jkl"}]}])
[{:foo {:bar/id [:bar/id :bar/name]
:baz/id [:baz/id :baz/name]}}])))
(t/is (= {:foo [{:bar/id 1
:bar/name "asdf"}
{:bar/id 2}
{:baz/id 1
:baz/name "jkl"}]}
(p/pull
(p/db [{:foo [{:bar/id 1
:bar/name "asdf"}
{:bar/id 2}
{:baz/id 1
:baz/name "jkl"}]}])
[{:foo {:bar/id [:bar/id :bar/name]
:baz/id [:baz/id :baz/name]}}]))))
(t/testing "bounded recursion"
(let [data {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders
[{:entry/id "asdf"
:entry/folders
[{:entry/id "qwerty"}]}
{:entry/id "jkl"
:entry/folders
[{:entry/id "uiop"}]}]}]}}
db (p/db [data])]
(t/is (= {:entries
{:entry/id "foo"
:entry/folders
[]}}
(p/pull db '[{:entries [:entry/id
{:entry/folders 0}]}])))
(t/is (= {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders []}]}}
(p/pull db '[{:entries [:entry/id
{:entry/folders 1}]}])))
(t/is (= {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders
[{:entry/id "asdf"
:entry/folders []}
{:entry/id "jkl"
:entry/folders []}]}]}}
(p/pull db '[{:entries [:entry/id
{:entry/folders 2}]}])))
(t/is (= {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders
[{:entry/id "asdf"
:entry/folders
[{:entry/id "qwerty"}]}
{:entry/id "jkl"
:entry/folders
[{:entry/id "uiop"}]}]}]}}
(p/pull db '[{:entries [:entry/id
{:entry/folders 3}]}])))
(t/is (= {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders
[{:entry/id "asdf"
:entry/folders
[{:entry/id "qwerty"}]}
{:entry/id "jkl"
:entry/folders
[{:entry/id "uiop"}]}]}]}}
(p/pull db '[{:entries [:entry/id
{:entry/folders 10}]}])))))
(t/testing "infinite recursion"
(let [data {:entries
{:entry/id "foo"
:entry/folders
[{:entry/id "bar"}
{:entry/id "baz"
:entry/folders
[{:entry/id "asdf"
:entry/folders
[{:entry/id "qwerty"}]}
{:entry/id "jkl"
:entry/folders
[{:entry/id "uiop"}]}]}]}}
db (p/db [data])]
(t/is (= data
(p/pull db '[{:entries [:entry/id
{:entry/folders ...}]}])))))
(t/testing "query metadata"
(t/is (-> db
(p/pull ^:foo [])
(meta)
(:foo))
"root")
(t/is (-> db
(p/pull [^:foo {[:person/id 0] [:person/name]}])
(get [:person/id 0])
(meta)
(:foo))
"join")
(let [data {:chat/entries
[{:message/id 0
:message/text "foo"
:chat.entry/timestamp "1234"}
{:message/id 1
:message/text "bar"
:chat.entry/timestamp "1235"}
{:audio/id 0
:audio/url "audio://asdf.jkl"
:audio/duration 1234
:chat.entry/timestamp "4567"}
{:photo/id 0
:photo/url "photo://asdf_10x10.jkl"
:photo/height 10
:photo/width 10
:chat.entry/timestamp "7890"}]}
db1 (p/db [data])
query ^:foo [^:bar
{:chat/entries
{:message/id
[:message/id :message/text :chat.entry/timestamp]
:audio/id
[:audio/id :audio/url :audio/duration :chat.entry/timestamp]
:photo/id
[:photo/id :photo/url :photo/width :photo/height :chat.entry/timestamp]
:asdf/jkl [:asdf/jkl]}}]
result (p/pull db1 query)]
(t/is (= #:chat{:entries [{:message/id 0
:message/text "foo"
:chat.entry/timestamp "1234"}
{:message/id 1
:message/text "bar"
:chat.entry/timestamp "1235"}
{:audio/id 0
:audio/url "audio://asdf.jkl"
:audio/duration 1234
:chat.entry/timestamp "4567"}
{:photo/id 0
:photo/url "photo://asdf_10x10.jkl"
:photo/width 10
:photo/height 10
:chat.entry/timestamp "7890"}]}
result))
(t/is (-> result meta :foo))
(t/is (every? #(:bar (meta %)) (get result :chat/entries)))))
(t/testing "dangling entities"
(t/is (= {[:id 0] {:friends [{:id 1} {:id 2}]}}
(p/pull
{:id {0 {:id 0 :name "asdf" :friends [[:id 1] [:id 2]]}
1 {:id 1 :name "jkl"}}}
[{[:id 0] [:friends]}]))
"dangling entity shows up in queries that do not select any props")
;; BB-TEST-PATCH: NullPointerException: Cannot invoke "clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)"
#_(t/is (= {[:id 0] {:friends [{:id 1, :name "jkl"} {:id 2}]}}
(p/pull
{:id {0 {:id 0 :name "asdf" :friends [[:id 1] [:id 2]]}
1 {:id 1 :name "jkl"}}}
[{[:id 0] [{:friends [:id :name]}]}]))
"dangling entity shows up in queries that include ID")
;; BB-TEST-PATCH: NullPointerException: Cannot invoke "clojure.lang.IObj.withMeta(clojure.lang.IPersistentMap)"
#_(t/is (= {[:id 0] {:friends [{:name "jkl"}]}}
(p/pull
{:id {0 {:id 0 :name "asdf" :friends [[:id 1] [:id 2]]}
1 {:id 1 :name "jkl"}}}
[{[:id 0] [{:friends [:name]}]}]))
"dangling entity does not show up in queries that do not include ID")))
(t/deftest pull-report
(t/is (= {:data {:people/all [{:person/name "Alice"}
{:person/name "Bob"}]}
:entities #{[:person/id 0] [:person/id 1]}}
(p/pull-report db [{:people/all [:person/name]}]))
"basic join + prop")
(t/is (= {:data #:people{:all [{:person/name "Alice"
:best-friend #:person{:name "Bob", :id 1 :age 23}}
#:person{:name "Bob"}]}
:entities #{[:person/id 0] [:person/id 1]}}
(p/pull-report db [#:people{:all [:person/name :best-friend]}]))
"join + prop + join ref lookup")
(t/is (= {:data {[:person/id 1] #:person{:id 1, :name "Bob", :age 23}}
:entities #{[:person/id 1]}}
(p/pull-report db [[:person/id 1]]))
"ident acts as ref lookup")
(t/is (= {:data {[:person/id 0] {:person/id 0
:person/name "Alice"
:person/age 25
:best-friend {:person/id 1}
:person/favorites #:favorite{:ice-cream "vanilla"}}}
:entities #{[:person/id 0]}}
(p/pull-report db [[:person/id 0]]))
"ident does not resolve nested refs"))
(t/deftest delete
(t/is (= {:people/all [[:person/id 0]]
:person/id {0 {:person/id 0
:person/name "Alice"
:person/age 25
:person/favorites #:favorite{:ice-cream "vanilla"}}}}
(p/delete db [:person/id 1]))))
(t/deftest data->query
(t/is (= [:a]
(p/data->query {:a 42})))
(t/is (= [{:a [:b]}]
(p/data->query {:a {:b 42}})))
(t/is (= [{:a [:b :c]}]
(p/data->query {:a [{:b 42} {:c :d}]})))
(t/is (= [{[:a 42] [:b]}]
(p/data->query {[:a 42] {:b 33}}))))
(comment
(t/run-tests))