diff options
Diffstat (limited to 'src/test/scala/xyz/driver')
8 files changed, 455 insertions, 11 deletions
diff --git a/src/test/scala/xyz/driver/pdsuicommon/Mocks.scala b/src/test/scala/xyz/driver/pdsuicommon/Mocks.scala index 1c01483..51d39e5 100644 --- a/src/test/scala/xyz/driver/pdsuicommon/Mocks.scala +++ b/src/test/scala/xyz/driver/pdsuicommon/Mocks.scala @@ -42,7 +42,6 @@ object MockMySqlContext { url = "" ), connection = ConfigFactory.empty(), - connectionAttemptsOnStartup = 1, threadPoolSize = 10 ) } diff --git a/src/test/scala/xyz/driver/pdsuicommon/parsers/PaginationParserSuite.scala b/src/test/scala/xyz/driver/pdsuicommon/parsers/PaginationParserSuite.scala new file mode 100644 index 0000000..48fc99b --- /dev/null +++ b/src/test/scala/xyz/driver/pdsuicommon/parsers/PaginationParserSuite.scala @@ -0,0 +1,95 @@ +package xyz.driver.pdsuicommon.parsers + +import xyz.driver.pdsuicommon.db.Pagination +import xyz.driver.pdsuicommon.parsers.TestUtils._ +import org.scalatest.{FreeSpecLike, MustMatchers} + +import scala.util.{Failure, Try} + +class PaginationParserSuite extends FreeSpecLike with MustMatchers { + + "parse" - { + "pageSize" - { + "should parse positive value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "10", + "pageNumber" -> "1" + )) + pagination must success + pagination.get.pageSize mustBe 10 + } + + "should return a default value if there is no one" in { + val pagination = PaginationParser.parse(Seq( + "pageNumber" -> "1" + )) + pagination must success + pagination.get.pageSize mustBe 100 + } + + "should return a error for zero value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "0", + "pageNumber" -> "1" + )) + + checkFailedValidationOnlyOn(pagination, "pageSize") + } + + "should return a error for negative value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "-10", + "pageNumber" -> "1" + )) + + checkFailedValidationOnlyOn(pagination, "pageSize") + } + } + + "pageNumber" - { + "should parse positive value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "1", + "pageNumber" -> "1" + )) + pagination must success + pagination.get.pageSize mustBe 1 + } + + "should return a default value if there is no one" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "1" + )) + pagination must success + pagination.get.pageNumber mustBe 1 + } + + "should return a error for zero value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "1", + "pageNumber" -> "0" + )) + + checkFailedValidationOnlyOn(pagination, "pageNumber") + } + + "should return a error for negative value" in { + val pagination = PaginationParser.parse(Seq( + "pageSize" -> "1", + "pageNumber" -> "-1" + )) + + checkFailedValidationOnlyOn(pagination, "pageNumber") + } + } + } + + private def checkFailedValidationOnlyOn(pagination: Try[Pagination], key: String): Unit = { + pagination must failWith[ParseQueryArgException] + + val Failure(e: ParseQueryArgException) = pagination + e.errors.size mustBe 1 + e.errors.head._1 mustBe key + } + +} diff --git a/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala b/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala new file mode 100644 index 0000000..f47f4c2 --- /dev/null +++ b/src/test/scala/xyz/driver/pdsuicommon/parsers/SearchFilterParserSuite.scala @@ -0,0 +1,207 @@ +package xyz.driver.pdsuicommon.parsers + +import xyz.driver.pdsuicommon.db.SearchFilterExpr.Dimension +import xyz.driver.pdsuicommon.db.{SearchFilterBinaryOperation, SearchFilterExpr, SearchFilterNAryOperation} +import xyz.driver.pdsuicommon.utils.Implicits.toStringOps +import xyz.driver.pdsuicommon.parsers.TestUtils._ +import fastparse.core.Parsed +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.{Gen, Prop} +import org.scalatest.FreeSpecLike +import org.scalatest.prop.Checkers +import xyz.driver.pdsuicommon.db.SearchFilterNAryOperation.In +import xyz.driver.pdsuicommon.utils.Utils +import xyz.driver.pdsuicommon.utils.Utils._ + +import scala.util.Success + +object SearchFilterParserSuite { + + class UnexpectedSearchFilterExprException(x: SearchFilterExpr) extends Exception(s"unexpected $x") + +} + +class SearchFilterParserSuite extends FreeSpecLike with Checkers { + + import SearchFilterParserSuite._ + + "parse" - { + "should convert column names to snake case" in { + import SearchFilterBinaryOperation._ + + val filter = SearchFilterParser.parse(Seq( + "filters" -> "status IN Summarized,ReviewCriteria,Flagged,Done", + "filters" -> "previousStatus NOTEQ New", + "filters" -> "previousStatus NOTEQ ReviewSummary" + )) + + assert(filter === Success(SearchFilterExpr.Intersection(List( + SearchFilterExpr.Atom.NAry(Dimension(None, "status"), In, Seq("Summarized", "ReviewCriteria", "Flagged", "Done")), + SearchFilterExpr.Atom.Binary(Dimension(None, "previous_status"), NotEq, "New"), + SearchFilterExpr.Atom.Binary(Dimension(None, "previous_status"), NotEq, "ReviewSummary"))))) + } + "dimensions" - { + "with table name" in check { + val dimensionGen = { + for (left <- Gen.identifier; right <- Gen.identifier) + yield left -> right + } + Prop.forAllNoShrink(dimensionGen) { + case (left, right) => + val raw = s"$left.$right" + val l = toSnakeCase(left) + val r = toSnakeCase(right) + SearchFilterParser.dimensionParser.parse(raw) match { + case Parsed.Success(Dimension(Some(`l`), `r`), _) => true + case _ => false + } + } + } + "just with field name" in check { + Prop.forAllNoShrink(Gen.identifier) { s => + val databaseS = Utils.toSnakeCase(s) + SearchFilterParser.dimensionParser.parse(s) match { + case Parsed.Success(Dimension(None, `databaseS`), _) => true + case _ => false + } + } + } + } + "atoms" - { + "binary" - { + "common operators" - { + "should be parsed with text values" in check { + import SearchFilterBinaryOperation._ + + val testQueryGen = queryGen( + dimensionGen = Gen.identifier, + opGen = commonBinaryOpsGen, + valueGen = nonEmptyString + ) + + Prop.forAllNoShrink(testQueryGen) { query => + SearchFilterParser.parse(Seq("filters" -> query)) + .map { + case SearchFilterExpr.Atom.Binary(_, Eq | NotEq | Like, _) => true + case x => throw new UnexpectedSearchFilterExprException(x) + } + .successProp + } + } + } + + "numeric operators" - { + "should not be parsed with text values" in check { + val testQueryGen = queryGen( + dimensionGen = Gen.identifier, + opGen = numericBinaryOpsGen, + valueGen = nonEmptyString.filter { s => !s.matches("^\\d+$") } + ) + + Prop.forAllNoShrink(testQueryGen) { query => + SearchFilterParser.parse(Seq("filters" -> query)).failureProp + } + } + } + + "all operators" - { + "should be parsed with numeric values" in check { + val testQueryGen = queryGen( + dimensionGen = Gen.identifier, + opGen = allBinaryOpsGen, + valueGen = numericBinaryAtomValuesGen + ) + + Prop.forAllNoShrink(testQueryGen) { query => + SearchFilterParser.parse(Seq("filters" -> query)) + .map { + case _: SearchFilterExpr.Atom.Binary => true + case x => throw new UnexpectedSearchFilterExprException(x) + } + .successProp + } + } + } + } + + "n-ary" - { + "in" in check { + val testQueryGen = queryGen( + dimensionGen = Gen.identifier, + opGen = Gen.const("in"), + valueGen = inValuesGen + ) + + Prop.forAllNoShrink(testQueryGen) { query => + SearchFilterParser.parse(Seq("filters" -> query)) + .map { + case SearchFilterExpr.Atom.NAry(_, SearchFilterNAryOperation.In, _) => true + case x => throw new UnexpectedSearchFilterExprException(x) + } + .successProp + } + } + } + } + + "intersections" - { + "should be parsed" in check { + val commonAtomsGen = queryGen( + dimensionGen = Gen.identifier, + opGen = commonBinaryOpsGen, + valueGen = nonEmptyString + ) + + val numericAtomsGen = queryGen( + dimensionGen = Gen.identifier, + opGen = numericBinaryOpsGen, + valueGen = numericBinaryAtomValuesGen + ) + + val allAtomsGen = Gen.oneOf(commonAtomsGen, numericAtomsGen) + val intersectionsGen = Gen.choose(1, 3).flatMap { size => + Gen.containerOfN[Seq, String](size, allAtomsGen) + } + + Prop.forAllNoShrink(intersectionsGen) { queries => + SearchFilterParser.parse(queries.map(query => "filters" -> query)).successProp + } + } + } + } + + private val CommonBinaryOps = Seq("eq", "noteq", "like") + private val NumericBinaryOps = Seq("gt", "gteq", "lt", "lteq") + + private val allBinaryOpsGen: Gen[String] = Gen.oneOf(CommonBinaryOps ++ NumericBinaryOps).flatMap(randomCapitalization) + private val commonBinaryOpsGen: Gen[String] = Gen.oneOf(CommonBinaryOps).flatMap(randomCapitalization) + private val numericBinaryOpsGen: Gen[String] = Gen.oneOf(NumericBinaryOps).flatMap(randomCapitalization) + + private val inValueCharsGen: Gen[Char] = arbitrary[Char].filter(_ != ',') + + private val nonEmptyString = arbitrary[String].filter { s => !s.safeTrim.isEmpty } + + private val numericBinaryAtomValuesGen: Gen[String] = arbitrary[BigInt].map(_.toString) + private val inValueGen: Gen[String] = { + Gen.nonEmptyContainerOf[Seq, Char](inValueCharsGen).map(_.mkString).filter(_.safeTrim.nonEmpty) + } + private val inValuesGen: Gen[String] = Gen.choose(1, 5).flatMap { size => + Gen.containerOfN[Seq, String](size, inValueGen).map(_.mkString(",")) + } + + private def queryGen(dimensionGen: Gen[String], opGen: Gen[String], valueGen: Gen[String]): Gen[String] = for { + dimension <- dimensionGen + op <- opGen + value <- valueGen + } yield s"$dimension $op $value" + + private def randomCapitalization(input: String): Gen[String] = { + Gen.containerOfN[Seq, Boolean](input.length, arbitrary[Boolean]).map { capitalize => + input.view.zip(capitalize).map { + case (currChar, true) => currChar.toUpper + case (currChar, false) => currChar + }.mkString + } + } + +} diff --git a/src/test/scala/xyz/driver/pdsuicommon/parsers/SortingParserSuite.scala b/src/test/scala/xyz/driver/pdsuicommon/parsers/SortingParserSuite.scala new file mode 100644 index 0000000..e46015c --- /dev/null +++ b/src/test/scala/xyz/driver/pdsuicommon/parsers/SortingParserSuite.scala @@ -0,0 +1,91 @@ +package xyz.driver.pdsuicommon.parsers + +import xyz.driver.pdsuicommon.parsers.TestUtils._ +import org.scalacheck.Arbitrary.arbitrary +import org.scalacheck.{Gen, Prop} +import org.scalatest.prop.Checkers +import org.scalatest.{FreeSpecLike, MustMatchers} + +class SortingParserSuite extends FreeSpecLike with MustMatchers with Checkers { + + "parse" - { + "single dimension" - commonTests(singleSortingQueryGen) + "multiple dimensions in one query" - commonTests(multipleSortingQueryGen) + "multiple queries" in { + val r = SortingParser.parse(Set("foo", "bar"), Seq("sort" -> "foo", "sort" ->"bar")) + r must failWith[ParseQueryArgException] + } + } + + private def commonTests(queryGen: Set[String] => Gen[String]): Unit = { + "valid" in check { + val inputGen: Gen[(Set[String], String)] = for { + validDimensions <- dimensionsGen + sorting <- queryGen(validDimensions) + } yield (validDimensions, sorting) + + Prop.forAllNoShrink(inputGen) { + case (validDimensions, query) => + SortingParser.parse(validDimensions, Seq("sort" -> query)).successProp + } + } + + "invalid" in check { + val inputGen: Gen[(Set[String], String)] = for { + validDimensions <- dimensionsGen + invalidDimensions <- dimensionsGen.filter { xs => xs.intersect(validDimensions).isEmpty } + sorting <- queryGen(invalidDimensions) + } yield (validDimensions, sorting) + + Prop.forAllNoShrink(inputGen) { + case (validDimensions, query) => + SortingParser.parse(validDimensions, Seq("sort" -> query)).failureProp + } + } + } + + private val dimensionsGen: Gen[Set[String]] = for { + unPrefixedSize <- Gen.choose(0, 3) + prefixedSize <- Gen.choose(0, 3) + if (unPrefixedSize + prefixedSize) > 0 + + unPrefixedDimensions <- Gen.containerOfN[Set, String](unPrefixedSize, Gen.identifier) + + prefixes <- Gen.containerOfN[Set, String](prefixedSize, Gen.identifier) + dimensions <- Gen.containerOfN[Set, String](prefixedSize, Gen.identifier) + } yield { + val prefixedDimensions = prefixes.zip(dimensions).map { + case (prefix, dimension) => s"$prefix.$dimension" + } + unPrefixedDimensions ++ prefixedDimensions + } + + private def multipleSortingQueryGen(validDimensions: Set[String]): Gen[String] = { + val validDimensionsSeq = validDimensions.toSeq + val indexGen = Gen.oneOf(validDimensionsSeq.indices) + val multipleDimensionsGen = Gen.nonEmptyContainerOf[Set, Int](indexGen).filter(_.size >= 2).map { indices => + indices.map(validDimensionsSeq.apply) + } + + for { + dimensions <- multipleDimensionsGen + isAscending <- Gen.containerOfN[Seq, Boolean](dimensions.size, arbitrary[Boolean]) + } yield { + isAscending.zip(dimensions) + .map { + case (true, dimension) => dimension + case (false, dimension) => "-" + dimension + } + .mkString(",") + } + } + + private def singleSortingQueryGen(validDimensions: Set[String]): Gen[String] = for { + isAscending <- arbitrary[Boolean] + dimensions <- Gen.oneOf(validDimensions.toSeq) + } yield isAscending match { + case true => dimensions + case false => "-" + dimensions + } + +} diff --git a/src/test/scala/xyz/driver/pdsuicommon/parsers/TestUtils.scala b/src/test/scala/xyz/driver/pdsuicommon/parsers/TestUtils.scala new file mode 100644 index 0000000..4892b95 --- /dev/null +++ b/src/test/scala/xyz/driver/pdsuicommon/parsers/TestUtils.scala @@ -0,0 +1,52 @@ +package xyz.driver.pdsuicommon.parsers + +import org.scalacheck.Prop +import org.scalacheck.Prop.BooleanOperators +import org.scalatest.matchers.{MatchResult, Matcher} +import xyz.driver.pdsuicommon.utils.Utils + +import scala.reflect.ClassTag +import scala.util.{Failure, Success, Try} + +object TestUtils { + + object success extends Matcher[Try[Any]] { + override def apply(left: Try[Any]) = { + MatchResult(left.isSuccess, s"$left did not fail", s"did fail with $left") + } + } + + class FailWith[ThrowableT <: Throwable](implicit ct: ClassTag[ThrowableT]) extends Matcher[Try[Any]] { + override def apply(left: Try[Any]): MatchResult = { + MatchResult( + left.isFailure && left.failed.get.getClass == ct.runtimeClass, + left match { + case Success(x) => s"$left did not fail" + case Failure(e) => s"$left did fail with ${Utils.getClassSimpleName(e.getClass)}, " + + s"not ${Utils.getClassSimpleName(ct.runtimeClass)}" + }, + left match { + case Success(_) => s"$left failed with ${Utils.getClassSimpleName(ct.runtimeClass)}" + case Failure(e) => s"$left failed with ${Utils.getClassSimpleName(e.getClass)}" + } + ) + } + } + + def failWith[ThrowableT <:Throwable](implicit ct: ClassTag[ThrowableT]) = new FailWith[ThrowableT] + + final implicit class TryPropOps(val self: Try[Any]) extends AnyVal { + + def successProp: Prop = self match { + case Success(_) => true :| "ok" + case Failure(e) => false :| e.getMessage + } + + def failureProp: Prop = self match { + case Success(x) => false :| s"invalid: $x" + case Failure(e) => true + } + + } + +} diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/DocumentIssueFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/DocumentIssueFormatSuite.scala index 1a8e3f0..c4c9f7c 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/DocumentIssueFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/DocumentIssueFormatSuite.scala @@ -22,10 +22,10 @@ class DocumentIssueFormatSuite extends FlatSpec with Matchers { startPage = Some(1.0), endPage = Some(2.0) ) - val writtenJson = documentIssueWriter.write(documentIssue) + val writtenJson = documentIssueFormat.write(documentIssue) writtenJson should be( - """{"id":10,"userId":"userId-001","lastUpdate":"2017-08-10T18:00Z","isDraft":false, + """{"id":10,"userId":"userId-001","documentId":1,"lastUpdate":"2017-08-10T18:00Z","isDraft":false, "text":"message text","archiveRequired":false,"startPage":1.0,"endPage":2.0}""".parseJson) val createDocumentIssueJson = """{"text":"message text","startPage":1.0,"endPage":2.0}""".parseJson diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala index 784a655..0f01d4a 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/InterventionFormatSuite.scala @@ -16,8 +16,8 @@ class InterventionFormatSuite extends FlatSpec with Matchers { originalName = "orig name", typeId = Some(LongId(10)), originalType = Some("orig type"), - description = "", - originalDescription = "", + dosage = "", + originalDosage = "", isActive = true ) val arms = List( @@ -32,12 +32,12 @@ class InterventionFormatSuite extends FlatSpec with Matchers { val writtenJson = interventionWriter.write(orig) writtenJson should be( - """{"id":1,"name":"intervention name","typeId":10,"description":"","isActive":true,"arms":[20,21,22], - "trialId":"NCT000001","originalName":"orig name","originalDescription":"","originalType":"orig type"}""".parseJson) + """{"id":1,"name":"intervention name","typeId":10,"dosage":"","isActive":true,"arms":[20,21,22], + "trialId":"NCT000001","originalName":"orig name","originalDosage":"","originalType":"orig type"}""".parseJson) - val updateInterventionJson = """{"description":"descr","arms":[21,22]}""".parseJson + val updateInterventionJson = """{"dosage":"descr","arms":[21,22]}""".parseJson val expectedUpdatedIntervention = orig.copy( - intervention = intervention.copy(description = "descr"), + intervention = intervention.copy(dosage = "descr"), arms = List( InterventionArm(interventionId = intervention.id, armId = LongId(21)), InterventionArm(interventionId = intervention.id, armId = LongId(22)) diff --git a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/MedicalRecordIssueFormatSuite.scala b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/MedicalRecordIssueFormatSuite.scala index 9b89c97..c23ca37 100644 --- a/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/MedicalRecordIssueFormatSuite.scala +++ b/src/test/scala/xyz/driver/pdsuidomain/formats/json/sprayformats/MedicalRecordIssueFormatSuite.scala @@ -22,10 +22,10 @@ class MedicalRecordIssueFormatSuite extends FlatSpec with Matchers { startPage = Some(1.0), endPage = Some(2.0) ) - val writtenJson = recordIssueWriter.write(recordIssue) + val writtenJson = recordIssueFormat.write(recordIssue) writtenJson should be( - """{"id":10,"userId":"userId-001","lastUpdate":"2017-08-10T18:00Z","isDraft":false, + """{"id":10,"recordId":1,"userId":"userId-001","lastUpdate":"2017-08-10T18:00Z","isDraft":false, "text":"message text","archiveRequired":false,"startPage":1.0,"endPage":2.0}""".parseJson) val createRecordIssueJson = """{"text":"message text","startPage":1.0,"endPage":2.0}""".parseJson |