PageRenderTime 54ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/salat-core/src/test/scala/com/novus/salat/test/dao/SalatDAOSpec.scala

https://github.com/kodemaniak/salat
Scala | 356 lines | 255 code | 59 blank | 42 comment | 0 complexity | 6bfb56927ea582f2a35a51d17a3efaa5 MD5 | raw file
Possible License(s): Apache-2.0
  1. /*
  2. * Copyright (c) 2010 - 2012 Novus Partners, Inc. (http://www.novus.com)
  3. *
  4. * Module: salat-core
  5. * Class: SalatDAOSpec.scala
  6. * Last modified: 2012-10-15 20:40:58 EDT
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. *
  20. * Project: http://github.com/novus/salat
  21. * Wiki: http://github.com/novus/salat/wiki
  22. * Mailing list: http://groups.google.com/group/scala-salat
  23. * StackOverflow: http://stackoverflow.com/questions/tagged/salat
  24. */
  25. package com.novus.salat.test.dao
  26. import com.novus.salat.test._
  27. import com.novus.salat._
  28. import com.novus.salat.test.global._
  29. import com.mongodb.casbah.Imports._
  30. import org.specs2.specification.Scope
  31. import com.novus.salat.util.MapPrettyPrinter
  32. import org.specs2.matcher.MustExpectable._
  33. import com.mongodb.casbah.commons.MongoDBObject
  34. import com.mongodb.DBObject
  35. class SalatDAOSpec extends SalatSpec {
  36. // which most specs can execute concurrently, this particular spec needs to execute sequentially to avoid mutating shared state,
  37. // namely, the MongoDB collection referenced by the AlphaDAO
  38. override def is = args(sequential = true) ^ super.is
  39. implicit val wc = AlphaDAO.defaultWriteConcern
  40. val alpha1 = Alpha(id = 1, beta = List[Beta](Gamma("gamma3"), Delta("delta3", "sampi3")))
  41. val alpha2 = Alpha(id = 2, beta = List[Beta](Gamma("gamma2"), Delta("delta2", "sampi2"), Delta("digamma2", "san2")))
  42. val alpha3 = Alpha(id = 3, beta = List[Beta](Gamma("gamma3"), Delta("delta3", "sampi3")))
  43. val alpha4 = Alpha(id = 4, beta = List[Beta](Delta("delta4", "koppa4")))
  44. val alpha5 = Alpha(id = 5, beta = List[Beta](Gamma("gamma5"), Gamma("gamma5-1")))
  45. val alpha6 = Alpha(id = 6, beta = List[Beta](Delta("delta6", "heta2"), Gamma("gamma6")))
  46. "Salat simple DAO" should {
  47. "supply a useful description for debugging" in {
  48. // default is SalatDAO[CaseClass,Id](collection name)
  49. AlphaDAO.description must_== "SalatDAO[Alpha,int](alpha_dao_spec)"
  50. }
  51. "insert a case class" in new alphaContext {
  52. val _id = AlphaDAO.insert(alpha3)
  53. _id must beSome(alpha3.id)
  54. AlphaDAO.collection.count must_== 1L
  55. val dbo: MongoDBObject = MongoConnection()(SalatSpecDb)(AlphaColl).findOne().get
  56. grater[Alpha].asObject(dbo) must_== alpha3
  57. }
  58. "insert a collection of case classes" in new alphaContext {
  59. // insert returns the typed contents of _id
  60. val _ids = AlphaDAO.insert(alpha4, alpha5, alpha6)
  61. _ids must contain(Some(alpha4.id))
  62. _ids must contain(Some(alpha5.id))
  63. _ids must contain(Some(alpha6.id))
  64. AlphaDAO.collection.count must_== 3L
  65. // the standard collection cursor returns DBOs
  66. val mongoCursor = AlphaDAO.collection.find()
  67. mongoCursor.next() must haveEntry("_id", alpha4.id)
  68. mongoCursor.next() must haveEntry("_id", alpha5.id)
  69. mongoCursor.next() must haveEntry("_id", alpha6.id)
  70. // BUT the Salat DAO returns a cursor types to case classes!
  71. val salatCursor = AlphaDAO.find(MongoDBObject.empty)
  72. salatCursor.next must_== alpha4
  73. salatCursor.next must_== alpha5
  74. salatCursor.next must_== alpha6
  75. }
  76. "no-op inserting an empty collection of objects" in {
  77. AlphaDAO.insert() must_== Nil
  78. }
  79. "support findOne returning Option[T]" in new alphaContext {
  80. val _ids = AlphaDAO.insert(alpha4, alpha5, alpha6)
  81. _ids must contain(Some(alpha4.id))
  82. _ids must contain(Some(alpha5.id))
  83. _ids must contain(Some(alpha6.id))
  84. AlphaDAO.collection.count must_== 3L
  85. // note: you can query using an object transformed into a dbo
  86. AlphaDAO.findOne(grater[Alpha].asDBObject(alpha6)) must beSome(alpha6)
  87. }
  88. "support findOneById returning Option[T]" in new alphaContext {
  89. val _ids = AlphaDAO.insert(alpha4, alpha5, alpha6)
  90. _ids must contain(Some(alpha4.id))
  91. _ids must contain(Some(alpha5.id))
  92. _ids must contain(Some(alpha6.id))
  93. AlphaDAO.collection.count must_== 3L
  94. AlphaDAO.findOneById(id = 5) must beSome(alpha5)
  95. }
  96. "support updating a case class" in new alphaContext {
  97. val _id = AlphaDAO.insert(alpha3)
  98. _id must beSome(alpha3.id)
  99. AlphaDAO.collection.count must_== 1L
  100. // need to explicitly specify upsert and multi when updating using an object instead of dbo
  101. val wr = AlphaDAO.update(q = MongoDBObject("_id" -> 3),
  102. t = alpha3.copy(beta = List[Beta](Gamma("gamma3"))),
  103. upsert = false,
  104. multi = false,
  105. wc = new WriteConcern())
  106. wr.getN must_== 1L
  107. AlphaDAO.collection.count must_== 1L
  108. val dbo: MongoDBObject = MongoConnection()(SalatSpecDb)(AlphaColl).findOne().get
  109. grater[Alpha].asObject(dbo) must_== alpha3.copy(beta = List[Beta](Gamma("gamma3")))
  110. }
  111. "support saving a case class" in new alphaContext {
  112. val _id = AlphaDAO.insert(alpha3)
  113. _id must beSome(alpha3.id)
  114. AlphaDAO.collection.count must_== 1L
  115. val alpha3_* = alpha3.copy(beta = List[Beta](Gamma("gamma3")))
  116. alpha3_* must_!= alpha3
  117. val wr = AlphaDAO.save(alpha3_*)
  118. wr.getN must_== 1L
  119. AlphaDAO.collection.count must_== 1L
  120. val dbo: MongoDBObject = MongoConnection()(SalatSpecDb)(AlphaColl).findOne().get
  121. grater[Alpha].asObject(dbo) must_== alpha3_*
  122. }
  123. "support removing a case class" in new alphaContext {
  124. val _ids = AlphaDAO.insert(alpha4, alpha5, alpha6)
  125. _ids must contain(Some(alpha4.id))
  126. _ids must contain(Some(alpha5.id))
  127. _ids must contain(Some(alpha6.id))
  128. AlphaDAO.collection.count must_== 3L
  129. val wr = AlphaDAO.remove(alpha5)
  130. wr.getN must_== 1L
  131. AlphaDAO.collection.count must_== 2L
  132. AlphaDAO.findOne(grater[Alpha].asDBObject(alpha5)) must beNone
  133. // and then there were two!
  134. val salatCursor = AlphaDAO.find(MongoDBObject.empty)
  135. salatCursor.next must_== alpha4
  136. salatCursor.next must_== alpha6
  137. }
  138. "support removing by ID" in new alphaContext {
  139. AlphaDAO.insert(alpha1)
  140. AlphaDAO.collection.count must_== 1L
  141. val wr = AlphaDAO.removeById(alpha1.id)
  142. wr.getN must_== 1L
  143. AlphaDAO.collection.count must_== 0L
  144. }
  145. "support removing by a list of IDs" in new alphaContext {
  146. val _ids = AlphaDAO.insert(alpha4, alpha5, alpha6)
  147. AlphaDAO.collection.count must_== 3L
  148. val wr = AlphaDAO.removeByIds(_ids.flatten)
  149. wr.getN must_== 3L
  150. AlphaDAO.collection.count must_== 0L
  151. }
  152. "support find returning a Mongo cursor typed to a case class" in new alphaContextWithData {
  153. val salatCursor = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$gte" -> 2)))
  154. salatCursor.next must_== alpha2
  155. salatCursor.next must_== alpha3
  156. salatCursor.next must_== alpha4
  157. salatCursor.next must_== alpha5
  158. salatCursor.next must_== alpha6
  159. salatCursor.hasNext must beFalse
  160. val salatCursor2 = AlphaDAO.find(ref = grater[Alpha].asDBObject(alpha6))
  161. salatCursor2.next must_== alpha6
  162. salatCursor2.hasNext must beFalse
  163. // works with limits!
  164. val salatCursor3 = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$gte" -> 2))).limit(2)
  165. salatCursor3.next must_== alpha2
  166. salatCursor3.next must_== alpha3
  167. salatCursor3.hasNext must beFalse
  168. // works with limits and skip
  169. val salatCursor4 = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$gte" -> 2)))
  170. .skip(2)
  171. .limit(1)
  172. salatCursor4.next must_== alpha4
  173. salatCursor4.hasNext must beFalse
  174. // works with limits and skip
  175. val salatCursor5 = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$gte" -> 2)))
  176. .sort(orderBy = MongoDBObject("_id" -> -1)) // sort by _id desc
  177. .skip(1)
  178. .limit(1)
  179. salatCursor5.next must_== alpha5
  180. salatCursor5.hasNext must beFalse
  181. }
  182. "support find with a set of keys" in new alphaContextWithData {
  183. val salatCursor = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$lt" -> 3)),
  184. keys = MongoDBObject("beta" -> 0)) // forces beta key to be excluded
  185. salatCursor.next must_== alpha1.copy(beta = Nil)
  186. salatCursor.next must_== alpha2.copy(beta = Nil)
  187. salatCursor.hasNext must beFalse
  188. }
  189. "support findOne with an object typed to the id" in new epsilonContext {
  190. val e = Epsilon(notes = "Just a test")
  191. val _id = EpsilonDAO.insert(e)
  192. _id must beSome(e.id)
  193. EpsilonDAO.collection.count must_== 1L
  194. val e_* = EpsilonDAO.findOne(grater[Epsilon].asDBObject(e))
  195. e_* must not beNone
  196. }
  197. "support using a query to bring back a typed list of ids" in new alphaContextWithData {
  198. val idList = AlphaDAO.ids(MongoDBObject("_id" -> MongoDBObject("$gt" -> 2)))
  199. idList must haveSize(4)
  200. idList must contain(3, 4, 5, 6)
  201. }
  202. "support using an iterator" in new alphaContextWithData {
  203. val results = AlphaDAO.find(ref = MongoDBObject("_id" -> MongoDBObject("$gte" -> 2)))
  204. .sort(orderBy = MongoDBObject("_id" -> -1)) // sort by _id desc
  205. .skip(1)
  206. .limit(1)
  207. .toList // yay!
  208. results must haveSize(1)
  209. results must contain(alpha5)
  210. }
  211. "support primitive projections" in new thetaContext {
  212. // a projection on a findOne that matches theta1
  213. ThetaDAO.primitiveProjection[String](MongoDBObject("x" -> "x1"), "y") must beSome("y1")
  214. // a projection on a findOne that brings nothing back
  215. ThetaDAO.primitiveProjection[String](MongoDBObject("x" -> "x99"), "y") must beNone
  216. val projList = ThetaDAO.primitiveProjections[String](MongoDBObject.empty, "y")
  217. projList must haveSize(4)
  218. projList must contain("y1", "y2", "y3", "y4") // theta5 has a null value for y, not in the list
  219. }
  220. "support using a projection on an Option field to filter out Nones" in new xiContext {
  221. // a projection on a findOne that matches xi1
  222. XiDAO.primitiveProjection[String](MongoDBObject("x" -> "x1"), "y") must beSome("y1")
  223. // a projection on a findOne that brings nothing back
  224. XiDAO.primitiveProjection[String](MongoDBObject("x" -> "x99"), "y") must beNone
  225. val projList = XiDAO.primitiveProjections[String](MongoDBObject.empty, "y")
  226. projList must haveSize(4)
  227. projList must contain("y1", "y2", "y3", "y4") // xi5 has a null value for y, not in the list
  228. }
  229. "support case class projections" in new kappaContext {
  230. // a projection on a findOne that matches kappa1
  231. KappaDAO.projection[Nu](MongoDBObject("k" -> "k1"), "nu") must beSome(nu1)
  232. // a projection on a findOne that brings nothing back
  233. KappaDAO.projection[Nu](MongoDBObject("k" -> "k99"), "nu") must beNone
  234. val projList = KappaDAO.projections[Nu](MongoDBObject("k" -> MongoDBObject("$in" -> List("k2", "k3"))), "nu")
  235. projList must haveSize(2)
  236. projList must contain(nu2, nu3)
  237. }
  238. }
  239. trait alphaContext extends Scope {
  240. log.debug("before: dropping %s", AlphaDAO.collection.getFullName())
  241. AlphaDAO.collection.drop()
  242. AlphaDAO.collection.count must_== 0L
  243. }
  244. trait alphaContextWithData extends Scope {
  245. log.debug("before: dropping %s", AlphaDAO.collection.getFullName())
  246. AlphaDAO.collection.drop()
  247. AlphaDAO.collection.count must_== 0L
  248. val _ids = AlphaDAO.insert(alpha1, alpha2, alpha3, alpha4, alpha5, alpha6)
  249. _ids must contain(Option(alpha1.id), Option(alpha2.id), Option(alpha3.id), Option(alpha4.id), Option(alpha5.id), Option(alpha6.id))
  250. AlphaDAO.collection.count must_== 6L
  251. }
  252. trait epsilonContext extends Scope {
  253. log.debug("before: dropping %s", EpsilonDAO.collection.getFullName())
  254. EpsilonDAO.collection.drop()
  255. EpsilonDAO.collection.count must_== 0L
  256. }
  257. trait thetaContext extends Scope {
  258. log.debug("before: dropping %s", ThetaDAO.collection.getFullName())
  259. ThetaDAO.collection.drop()
  260. ThetaDAO.collection.count must_== 0L
  261. val theta1 = Theta(x = "x1", y = "y1")
  262. val theta2 = Theta(x = "x2", y = "y2")
  263. val theta3 = Theta(x = "x3", y = "y3")
  264. val theta4 = Theta(x = "x4", y = "y4")
  265. val theta5 = Theta(x = "x5", y = null)
  266. val _ids = ThetaDAO.insert(theta1, theta2, theta3, theta4, theta5)
  267. _ids must contain(Option(theta1.id), Option(theta2.id), Option(theta3.id), Option(theta4.id), Option(theta5.id))
  268. ThetaDAO.collection.count must_== 5L
  269. }
  270. trait xiContext extends Scope {
  271. log.debug("before: dropping %s", XiDAO.collection.getFullName())
  272. XiDAO.collection.drop()
  273. XiDAO.collection.count must_== 0L
  274. val xi1 = Xi(x = "x1", y = Some("y1"))
  275. val xi2 = Xi(x = "x2", y = Some("y2"))
  276. val xi3 = Xi(x = "x3", y = Some("y3"))
  277. val xi4 = Xi(x = "x4", y = Some("y4"))
  278. val xi5 = Xi(x = "x5", y = None)
  279. val _ids = XiDAO.insert(xi1, xi2, xi3, xi4, xi5)
  280. _ids must contain(Option(xi1.id), Option(xi2.id), Option(xi3.id), Option(xi4.id), Option(xi5.id))
  281. XiDAO.collection.count must_== 5L
  282. }
  283. trait kappaContext extends Scope {
  284. log.debug("before: dropping %s", KappaDAO.collection.getFullName())
  285. KappaDAO.collection.drop()
  286. KappaDAO.collection.count must_== 0L
  287. val nu1 = Nu(x = "x1", y = "y1")
  288. val nu2 = Nu(x = "x2", y = "y2")
  289. val nu3 = Nu(x = "x3", y = "y3")
  290. val kappa1 = Kappa(k = "k1", nu = nu1)
  291. val kappa2 = Kappa(k = "k2", nu = nu2)
  292. val kappa3 = Kappa(k = "k3", nu = nu3)
  293. val _ids = KappaDAO.insert(kappa1, kappa2, kappa3)
  294. _ids must contain(Option(kappa1.id), Option(kappa2.id), Option(kappa3.id))
  295. KappaDAO.collection.count must_== 3L
  296. }
  297. }