From c3cdec416c90ebc3e08ed8f97b27ebfe2dc113c5 Mon Sep 17 00:00:00 2001 From: Eike Kettner Date: Wed, 24 Feb 2021 00:22:45 +0100 Subject: [PATCH] Sketching some basic tests --- build.sbt | 6 +- .../docspell/query/ItemQueryParser.scala | 2 + .../qb/generator/ItemQueryGenerator.scala | 27 +++++--- .../scala/docspell/store/StoreFixture.scala | 67 +++++++++++++++++++ .../generator/ItemQueryGeneratorTest.scala | 63 +++++++++++++++++ 5 files changed, 153 insertions(+), 12 deletions(-) create mode 100644 modules/store/src/test/scala/docspell/store/StoreFixture.scala create mode 100644 modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala diff --git a/build.sbt b/build.sbt index 7cb00d86..8b31ed2f 100644 --- a/build.sbt +++ b/build.sbt @@ -47,7 +47,8 @@ val sharedSettings = Seq( val testSettings = Seq( testFrameworks += new TestFramework("minitest.runner.Framework"), - libraryDependencies ++= Dependencies.miniTest ++ Dependencies.logging.map(_ % Test) + libraryDependencies ++= Dependencies.miniTest ++ Dependencies.logging.map(_ % Test), + Test / fork := true ) lazy val noPublish = Seq( @@ -275,6 +276,9 @@ val query = libraryDependencies += Dependencies.catsParseJS.value ) + .jsSettings( + Test / fork := false + ) .jvmSettings( libraryDependencies += Dependencies.scalaJsStubs diff --git a/modules/query/src/main/scala/docspell/query/ItemQueryParser.scala b/modules/query/src/main/scala/docspell/query/ItemQueryParser.scala index 23b0c297..7fc3a87e 100644 --- a/modules/query/src/main/scala/docspell/query/ItemQueryParser.scala +++ b/modules/query/src/main/scala/docspell/query/ItemQueryParser.scala @@ -15,4 +15,6 @@ object ItemQueryParser { .map(_.toString) .map(expr => ItemQuery(expr, Some(input.trim))) + def parseUnsafe(input: String): ItemQuery = + parse(input).fold(sys.error, identity) } diff --git a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala index b43764c4..2f745dd8 100644 --- a/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala +++ b/modules/store/src/main/scala/docspell/store/qb/generator/ItemQueryGenerator.scala @@ -1,11 +1,11 @@ package docspell.store.qb.generator +import java.time.{Instant, LocalDate} + import cats.data.NonEmptyList import docspell.common._ -import docspell.query.ItemQuery -import docspell.query.ItemQuery.Attr._ -import docspell.query.ItemQuery.Property.{DateProperty, StringProperty} -import docspell.query.ItemQuery.{Attr, Expr, Operator, TagOperator} +import docspell.query.{Date, ItemQuery} +import docspell.query.ItemQuery._ import docspell.store.qb.{Operator => QOp, _} import docspell.store.qb.DSL._ import docspell.store.records.{RCustomField, RCustomFieldValue, TagItemName} @@ -74,7 +74,7 @@ object ItemQueryGenerator { case Expr.Exists(field) => anyColumn(tables)(field).isNotNull - case Expr.SimpleExpr(op, StringProperty(attr, value)) => + case Expr.SimpleExpr(op, Property.StringProperty(attr, value)) => val col = stringColumn(tables)(attr) op match { case Operator.Like => @@ -83,8 +83,13 @@ object ItemQueryGenerator { Condition.CompareVal(col, makeOp(op), value) } - case Expr.SimpleExpr(op, DateProperty(attr, value)) => - val dt = Timestamp.atUtc(value.atStartOfDay()) + case Expr.SimpleExpr(op, Property.DateProperty(attr, value)) => + val dt = value match { + case Date.Local(year, month, day) => + Timestamp.atUtc(LocalDate.of(year, month, day).atStartOfDay()) + case Date.Millis(ms) => + Timestamp(Instant.ofEpochMilli(ms)) + } val col = timestampColumn(tables)(attr) Condition.CompareVal(col, makeOp(op), dt) @@ -135,13 +140,13 @@ object ItemQueryGenerator { private def anyColumn(tables: Tables)(attr: Attr): Column[_] = attr match { - case s: StringAttr => + case s: Attr.StringAttr => stringColumn(tables)(s) - case t: DateAttr => + case t: Attr.DateAttr => timestampColumn(tables)(t) } - private def timestampColumn(tables: Tables)(attr: DateAttr) = + private def timestampColumn(tables: Tables)(attr: Attr.DateAttr) = attr match { case Attr.Date => tables.item.itemDate @@ -149,7 +154,7 @@ object ItemQueryGenerator { tables.item.dueDate } - private def stringColumn(tables: Tables)(attr: StringAttr): Column[String] = + private def stringColumn(tables: Tables)(attr: Attr.StringAttr): Column[String] = attr match { case Attr.ItemId => tables.item.id.cast[String] case Attr.ItemName => tables.item.name diff --git a/modules/store/src/test/scala/docspell/store/StoreFixture.scala b/modules/store/src/test/scala/docspell/store/StoreFixture.scala new file mode 100644 index 00000000..839eb242 --- /dev/null +++ b/modules/store/src/test/scala/docspell/store/StoreFixture.scala @@ -0,0 +1,67 @@ +package docspell.store + +import cats.effect._ +import docspell.common.LenientUri +import docspell.store.impl.StoreImpl +import doobie._ +import org.h2.jdbcx.JdbcConnectionPool + +import scala.concurrent.ExecutionContext + +trait StoreFixture { + def withStore(db: String)(code: Store[IO] => IO[Unit]): Unit = { + //StoreFixture.store(StoreFixture.memoryDB(db)).use(code).unsafeRunSync() + val jdbc = StoreFixture.memoryDB(db) + val xa = StoreFixture.globalXA(jdbc) + val store = new StoreImpl[IO](jdbc, xa) + store.migrate.unsafeRunSync() + code(store).unsafeRunSync() + } + + def withXA(db: String)(code: Transactor[IO] => IO[Unit]): Unit = + StoreFixture.makeXA(StoreFixture.memoryDB(db)).use(code).unsafeRunSync() + +} + +object StoreFixture { + implicit def contextShift: ContextShift[IO] = + IO.contextShift(ExecutionContext.global) + + def memoryDB(dbname: String): JdbcConfig = + JdbcConfig( + LenientUri.unsafe( + s"jdbc:h2:mem:$dbname;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE;DB_CLOSE_DELAY=-1" + ), + "sa", + "" + ) + + def globalXA(jdbc: JdbcConfig): Transactor[IO] = + Transactor.fromDriverManager( + "org.h2.Driver", + jdbc.url.asString, + jdbc.user, + jdbc.password + ) + + def makeXA(jdbc: JdbcConfig): Resource[IO, Transactor[IO]] = { + def jdbcConnPool = + JdbcConnectionPool.create(jdbc.url.asString, jdbc.user, jdbc.password) + + val makePool = Resource.make(IO(jdbcConnPool))(cp => IO(cp.dispose())) + + for { + ec <- ExecutionContexts.cachedThreadPool[IO] + blocker <- Blocker[IO] + pool <- makePool + xa = Transactor.fromDataSource[IO].apply(pool, ec, blocker) + } yield xa + } + + def store(jdbc: JdbcConfig): Resource[IO, Store[IO]] = + for { + xa <- makeXA(jdbc) + store = new StoreImpl[IO](jdbc, xa) + _ <- Resource.liftF(store.migrate) + } yield store +} diff --git a/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala b/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala new file mode 100644 index 00000000..7bbfcb52 --- /dev/null +++ b/modules/store/src/test/scala/docspell/store/generator/ItemQueryGeneratorTest.scala @@ -0,0 +1,63 @@ +package docspell.store.generator + +import java.time.LocalDate + +import docspell.store.records._ +import minitest._ +import docspell.common._ +import docspell.query.ItemQueryParser +import docspell.store.qb.DSL._ +import docspell.store.qb.generator.{ItemQueryGenerator, Tables} + +object ItemQueryGeneratorTest extends SimpleTestSuite { + import docspell.store.impl.DoobieMeta._ + + val tables = Tables( + RItem.as("i"), + ROrganization.as("co"), + RPerson.as("cp"), + RPerson.as("np"), + REquipment.as("ne"), + RFolder.as("f"), + RAttachment.as("a"), + RAttachmentMeta.as("m") + ) + + test("migration") { + val q = ItemQueryParser + .parseUnsafe("(& name:hello date>=2020-02-01 (| source=expense folder=test ))") + val cond = ItemQueryGenerator(tables, Ident.unsafe("coll"))(q) + val expect = + tables.item.name.like("hello") && tables.item.itemDate >= Timestamp.atUtc( + LocalDate.of(2020, 2, 1).atStartOfDay() + ) && (tables.item.source === "expense" || tables.folder.name === "test") + + assertEquals(cond, expect) + } + +// test("migration2") { +// withStore("db2") { store => +// val c = RCollective( +// Ident.unsafe("coll1"), +// CollectiveState.Active, +// Language.German, +// true, +// Timestamp.Epoch +// ) +// val e = +// REquipment( +// Ident.unsafe("equip"), +// Ident.unsafe("coll1"), +// "name", +// Timestamp.Epoch, +// Timestamp.Epoch, +// None +// ) +// +// for { +// _ <- store.transact(RCollective.insert(c)) +// _ <- store.transact(REquipment.insert(e)).map(_ => ()) +// } yield () +// } +// } +}