From 02810f5eac3b4ce6a5d1128281a01b2a2ed0647c Mon Sep 17 00:00:00 2001 From: vlad Date: Fri, 21 Oct 2016 16:07:05 -0400 Subject: Renamed package to xyz, New formatting, authorize directive supporting multiple permissions --- src/test/scala/com/drivergrp/core/AuthTest.scala | 77 ------- src/test/scala/com/drivergrp/core/CoreTest.scala | 61 ------ src/test/scala/com/drivergrp/core/FileTest.scala | 126 ----------- .../scala/com/drivergrp/core/GeneratorsTest.scala | 234 --------------------- src/test/scala/com/drivergrp/core/JsonTest.scala | 101 --------- .../scala/com/drivergrp/core/MessagesTest.scala | 80 ------- src/test/scala/com/drivergrp/core/StatsTest.scala | 43 ---- src/test/scala/com/drivergrp/core/TimeTest.scala | 87 -------- src/test/scala/xyz/driver/core/AuthTest.scala | 77 +++++++ src/test/scala/xyz/driver/core/CoreTest.scala | 61 ++++++ src/test/scala/xyz/driver/core/FileTest.scala | 126 +++++++++++ .../scala/xyz/driver/core/GeneratorsTest.scala | 234 +++++++++++++++++++++ src/test/scala/xyz/driver/core/JsonTest.scala | 101 +++++++++ src/test/scala/xyz/driver/core/MessagesTest.scala | 80 +++++++ src/test/scala/xyz/driver/core/StatsTest.scala | 43 ++++ src/test/scala/xyz/driver/core/TimeTest.scala | 87 ++++++++ 16 files changed, 809 insertions(+), 809 deletions(-) delete mode 100644 src/test/scala/com/drivergrp/core/AuthTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/CoreTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/FileTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/GeneratorsTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/JsonTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/MessagesTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/StatsTest.scala delete mode 100644 src/test/scala/com/drivergrp/core/TimeTest.scala create mode 100644 src/test/scala/xyz/driver/core/AuthTest.scala create mode 100644 src/test/scala/xyz/driver/core/CoreTest.scala create mode 100644 src/test/scala/xyz/driver/core/FileTest.scala create mode 100644 src/test/scala/xyz/driver/core/GeneratorsTest.scala create mode 100644 src/test/scala/xyz/driver/core/JsonTest.scala create mode 100644 src/test/scala/xyz/driver/core/MessagesTest.scala create mode 100644 src/test/scala/xyz/driver/core/StatsTest.scala create mode 100644 src/test/scala/xyz/driver/core/TimeTest.scala (limited to 'src/test/scala') diff --git a/src/test/scala/com/drivergrp/core/AuthTest.scala b/src/test/scala/com/drivergrp/core/AuthTest.scala deleted file mode 100644 index 42f9155..0000000 --- a/src/test/scala/com/drivergrp/core/AuthTest.scala +++ /dev/null @@ -1,77 +0,0 @@ -package com.drivergrp.core - -import com.drivergrp.core.auth._ -import akka.http.scaladsl.testkit.ScalatestRouteTest -import akka.http.scaladsl.server._ -import Directives._ -import akka.http.scaladsl.model.headers.{HttpChallenges, RawHeader} -import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected -import org.scalatest.mock.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} - -import scala.concurrent.Future -import scalaz.OptionT - -class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRouteTest { - - val authStatusService: AuthService[User] = new AuthService[User] { - override def authStatus(authToken: AuthToken): OptionT[Future, User] = OptionT.optionT[Future] { - Future.successful(Some(new User() { - override def id: Id[User] = Id[User](1L) - override def roles: Set[Role] = Set(PathologistRole) - })) - } - } - - import authStatusService._ - - "'authorize' directive" should "throw error is auth token is not in the request" in { - - Get("/naive/attempt") ~> - authorize(CanSignOutReport) { - case (authToken, user) => - complete("Never going to be here") - } ~> - check { - handled shouldBe false - rejections should contain(MissingHeaderRejection("WWW-Authenticate")) - } - } - - it should "throw error is authorized user is not having the requested permission" in { - - val referenceAuthToken = AuthToken(Base64("I am a pathologist's token")) - - Post("/administration/attempt").addHeader( - RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value) - ) ~> - authorize(CanAssignRoles) { - case (authToken, user) => - complete("Never going to get here") - } ~> - check { - handled shouldBe false - rejections should contain( - AuthenticationFailedRejection( - CredentialsRejected, - HttpChallenges.basic("User does not have the required permission CanAssignRoles"))) - } - } - - it should "pass and retrieve the token to client code, if token is in request and user has permission" in { - - val referenceAuthToken = AuthToken(Base64("I am token")) - - Get("/valid/attempt/?a=2&b=5").addHeader( - RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value) - ) ~> - authorize(CanSignOutReport) { - case (authToken, user) => - complete("Alright, \"" + authToken.value.value + "\" is handled") - } ~> - check { - handled shouldBe true - responseAs[String] shouldBe "Alright, \"I am token\" is handled" - } - } -} diff --git a/src/test/scala/com/drivergrp/core/CoreTest.scala b/src/test/scala/com/drivergrp/core/CoreTest.scala deleted file mode 100644 index 19e685c..0000000 --- a/src/test/scala/com/drivergrp/core/CoreTest.scala +++ /dev/null @@ -1,61 +0,0 @@ -package com.drivergrp.core - -import java.io.ByteArrayOutputStream - -import com.drivergrp.core.revision.Revision -import org.scalatest.mock.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} -import org.mockito.Mockito._ - -class CoreTest extends FlatSpec with Matchers with MockitoSugar { - - "'make' function" should "allow initialization for objects" in { - - val createdAndInitializedValue = make(new ByteArrayOutputStream(128)) { baos => - baos.write(Array(1.toByte, 1.toByte, 0.toByte)) - } - - createdAndInitializedValue.toByteArray should be(Array(1.toByte, 1.toByte, 0.toByte)) - } - - "'using' function" should "call close after performing action on resource" in { - - val baos = mock[ByteArrayOutputStream] - - using(baos /* usually new ByteArrayOutputStream(128) */ ) { baos => - baos.write(Array(1.toByte, 1.toByte, 0.toByte)) - } - - verify(baos).close() - } - - "Id" should "have equality and ordering working correctly" in { - - (Id[String](1234213L) === Id[String](1234213L)) should be(true) - (Id[String](1234213L) === Id[String](213414L)) should be(false) - (Id[String](213414L) === Id[String](1234213L)) should be(false) - - Seq(Id[String](4L), Id[String](3L), Id[String](2L), Id[String](1L)).sorted should contain - theSameElementsInOrderAs(Seq(Id[String](1L), Id[String](2L), Id[String](3L), Id[String](4L))) - } - - "Name" should "have equality and ordering working correctly" in { - - (Name[String]("foo") === Name[String]("foo")) should be(true) - (Name[String]("foo") === Name[String]("bar")) should be(false) - (Name[String]("bar") === Name[String]("foo")) should be(false) - - Seq(Name[String]("d"), Name[String]("cc"), Name[String]("a"), Name[String]("bbb")).sorted should contain - theSameElementsInOrderAs(Seq(Name[String]("a"), Name[String]("bbb"), Name[String]("cc"), Name[String]("d"))) - } - - "Revision" should "have equality working correctly" in { - - val bla = Revision[String]("85569dab-a3dc-401b-9f95-d6fb4162674b") - val foo = Revision[String]("f54b3558-bdcd-4646-a14b-8beb11f6b7c4") - - (bla === bla) should be(true) - (bla === foo) should be(false) - (foo === bla) should be(false) - } -} diff --git a/src/test/scala/com/drivergrp/core/FileTest.scala b/src/test/scala/com/drivergrp/core/FileTest.scala deleted file mode 100644 index 2c9c2c9..0000000 --- a/src/test/scala/com/drivergrp/core/FileTest.scala +++ /dev/null @@ -1,126 +0,0 @@ -package com.drivergrp.core - -import java.io.File -import java.nio.file.Paths - -import com.amazonaws.services.s3.AmazonS3 -import com.amazonaws.services.s3.model._ -import com.drivergrp.core.file.{FileSystemStorage, S3Storage} -import org.scalatest.mock.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} -import org.mockito.Mockito._ -import org.mockito.Matchers._ - -import scala.concurrent.Await -import scala.concurrent.duration._ - -class FileTest extends FlatSpec with Matchers with MockitoSugar { - - "S3 Storage" should "create and download local files and do other operations" in { - import scala.collection.JavaConverters._ - - val tempDir = System.getProperty("java.io.tmpdir") - val sourceTestFile = generateTestLocalFile(tempDir) - val testFileName = "uploadTestFile" - - val randomFolderName = java.util.UUID.randomUUID().toString - val testDirPath = Paths.get(randomFolderName) - val testFilePath = Paths.get(randomFolderName, testFileName) - - val testBucket = Name[Bucket]("IamBucket") - - val s3PutMock = mock[PutObjectResult] - when(s3PutMock.getETag).thenReturn("IAmEtag") - - val s3ObjectSummaryMock = mock[S3ObjectSummary] - when(s3ObjectSummaryMock.getKey).thenReturn(testFileName) - when(s3ObjectSummaryMock.getETag).thenReturn("IAmEtag") - when(s3ObjectSummaryMock.getLastModified).thenReturn(new java.util.Date()) - - val s3ResultsMock = mock[ListObjectsV2Result] - when(s3ResultsMock.getNextContinuationToken).thenReturn("continuationToken") - when(s3ResultsMock.isTruncated).thenReturn(false, // before file created it is empty (zero pages) - true, - false, // after file is uploaded it contains this one file (one page) - false) // after file is deleted it is empty (zero pages) again - when(s3ResultsMock.getObjectSummaries).thenReturn( - // before file created it is empty, `getObjectSummaries` is never called - List[S3ObjectSummary](s3ObjectSummaryMock).asJava, // after file is uploaded it contains this one file - List.empty[S3ObjectSummary].asJava) // after file is deleted it is empty again - - val s3ObjectMetadataMock = mock[ObjectMetadata] - val amazonS3Mock = mock[AmazonS3] - when(amazonS3Mock.listObjectsV2(any[ListObjectsV2Request]())).thenReturn(s3ResultsMock) - when(amazonS3Mock.putObject(testBucket, testFilePath.toString, sourceTestFile)).thenReturn(s3PutMock) - when(amazonS3Mock.getObject(any[GetObjectRequest](), any[File]())).thenReturn(s3ObjectMetadataMock) - - val s3Storage = new S3Storage(amazonS3Mock, testBucket, scala.concurrent.ExecutionContext.global) - - val filesBefore = Await.result(s3Storage.list(testDirPath).run, 10 seconds) - filesBefore shouldBe empty - - Await.result(s3Storage.upload(sourceTestFile, testFilePath), 10 seconds) - - val filesAfterUpload = Await.result(s3Storage.list(testDirPath).run, 10 seconds) - filesAfterUpload.size should be(1) - val uploadedFileLine = filesAfterUpload.head - uploadedFileLine.name should be(Name[File](testFileName)) - uploadedFileLine.location should be(testFilePath) - uploadedFileLine.revision.id.length should be > 0 - uploadedFileLine.lastModificationDate.millis should be > 0L - - val downloadedFile = Await.result(s3Storage.download(testFilePath).run, 10 seconds) - downloadedFile shouldBe defined - downloadedFile.foreach { - _.getAbsolutePath.endsWith(testFilePath.toString) should be(true) - } - - Await.result(s3Storage.delete(testFilePath), 10 seconds) - - val filesAfterRemoval = Await.result(s3Storage.list(testDirPath).run, 10 seconds) - filesAfterRemoval shouldBe empty - } - - "Filesystem files storage" should "create and download local files and do other operations" in { - - val tempDir = System.getProperty("java.io.tmpdir") - val sourceTestFile = generateTestLocalFile(tempDir) - - val randomFolderName = java.util.UUID.randomUUID().toString - val testDirPath = Paths.get(tempDir, randomFolderName) - val testFilePath = Paths.get(tempDir, randomFolderName, "uploadTestFile") - - val fileStorage = new FileSystemStorage(scala.concurrent.ExecutionContext.global) - - val filesBefore = Await.result(fileStorage.list(testDirPath).run, 10 seconds) - filesBefore shouldBe empty - - Await.result(fileStorage.upload(sourceTestFile, testFilePath), 10 seconds) - - val filesAfterUpload = Await.result(fileStorage.list(testDirPath).run, 10 seconds) - filesAfterUpload.size should be(1) - val uploadedFileLine = filesAfterUpload.head - uploadedFileLine.name should be(Name[File]("uploadTestFile")) - uploadedFileLine.location should be(testFilePath) - uploadedFileLine.revision.id.length should be > 0 - uploadedFileLine.lastModificationDate.millis should be > 0L - - val downloadedFile = Await.result(fileStorage.download(testFilePath).run, 10 seconds) - downloadedFile shouldBe defined - downloadedFile.map(_.getAbsolutePath) should be(Some(testFilePath.toString)) - - Await.result(fileStorage.delete(testFilePath), 10 seconds) - - val filesAfterRemoval = Await.result(fileStorage.list(testDirPath).run, 10 seconds) - filesAfterRemoval shouldBe empty - } - - private def generateTestLocalFile(path: String): File = { - val randomSourceFolderName = java.util.UUID.randomUUID().toString - val sourceTestFile = new File(Paths.get(path, randomSourceFolderName, "uploadTestFile").toString) - sourceTestFile.getParentFile.mkdirs() should be(true) - sourceTestFile.createNewFile() should be(true) - using(new java.io.PrintWriter(sourceTestFile)) { _.append("Test File Contents") } - sourceTestFile - } -} diff --git a/src/test/scala/com/drivergrp/core/GeneratorsTest.scala b/src/test/scala/com/drivergrp/core/GeneratorsTest.scala deleted file mode 100644 index 631149e..0000000 --- a/src/test/scala/com/drivergrp/core/GeneratorsTest.scala +++ /dev/null @@ -1,234 +0,0 @@ -package com.drivergrp.core - -import org.scalatest.{Assertions, FlatSpec, Matchers} - -class GeneratorsTest extends FlatSpec with Matchers with Assertions { - import generators._ - - "Generators" should "be able to generate com.drivergrp.core.Id identifiers" in { - - val generatedId1 = nextId[String]() - val generatedId2 = nextId[String]() - val generatedId3 = nextId[Long]() - - generatedId1 should be >= 0L - generatedId2 should be >= 0L - generatedId3 should be >= 0L - generatedId1 should not be generatedId2 - generatedId2 should !==(generatedId3) - } - - it should "be able to generate com.drivergrp.core.Id identifiers with max value" in { - - val generatedLimitedId1 = nextId[String](10000) - val generatedLimitedId2 = nextId[String](1000) - val generatedLimitedId3 = nextId[Long](2000) - - generatedLimitedId1 should be >= 0L - generatedLimitedId1 should be < 10000L - generatedLimitedId2 should be >= 0L - generatedLimitedId2 should be < 1000L - generatedLimitedId3 should be >= 0L - generatedLimitedId3 should be < 2000L - generatedLimitedId1 should not be generatedLimitedId2 - generatedLimitedId2 should !==(generatedLimitedId3) - } - - it should "be able to generate com.drivergrp.core.Name names" in { - - nextName[String]() should not be nextName[String]() - nextName[String]().length should be >= 0 - - val fixedLengthName = nextName[String](10) - fixedLengthName.length should be <= 10 - assert(!fixedLengthName.exists(_.isControl)) - } - - it should "be able to generate proper UUIDs" in { - - nextUuid() should not be nextUuid() - nextUuid().toString.length should be(36) - } - - it should "be able to generate new Revisions" in { - - nextRevision[String]() should not be nextRevision[String]() - nextRevision[String]().id.length should be > 0 - } - - it should "be able to generate strings" in { - - nextString() should not be nextString() - nextString().length should be >= 0 - - val fixedLengthString = nextString(20) - fixedLengthString.length should be <= 20 - assert(!fixedLengthString.exists(_.isControl)) - } - - it should "be able to generate options which are sometimes have values and sometimes not" in { - - val generatedOption = nextOption("2") - - generatedOption should not contain "1" - assert(generatedOption === Some("2") || generatedOption === None) - } - - it should "be able to generate a pair of two generated values" in { - - val constantPair = nextPair("foo", 1L) - constantPair._1 should be("foo") - constantPair._2 should be(1L) - - val generatedPair = nextPair(nextId[Int](), nextName[Int]()) - - generatedPair._1 should be > 0L - generatedPair._2.length should be > 0 - - nextPair(nextId[Int](), nextName[Int]()) should not be - nextPair(nextId[Int](), nextName[Int]()) - } - - it should "be able to generate a triad of two generated values" in { - - val constantTriad = nextTriad("foo", "bar", 1L) - constantTriad._1 should be("foo") - constantTriad._2 should be("bar") - constantTriad._3 should be(1L) - - val generatedTriad = nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) - - generatedTriad._1 should be > 0L - generatedTriad._2.length should be > 0 - generatedTriad._3 should be >= BigDecimal(0.00) - - nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) should not be - nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) - } - - it should "be able to generate a time value" in { - - val generatedTime = nextTime() - val currentTime = System.currentTimeMillis() - - generatedTime.millis should be >= 0L - generatedTime.millis should be <= currentTime - } - - it should "be able to generate a time range value" in { - - val generatedTimeRange = nextTimeRange() - val currentTime = System.currentTimeMillis() - - generatedTimeRange.start.millis should be >= 0L - generatedTimeRange.start.millis should be <= currentTime - generatedTimeRange.end.millis should be >= 0L - generatedTimeRange.end.millis should be <= currentTime - generatedTimeRange.start.millis should be <= generatedTimeRange.end.millis - } - - it should "be able to generate a BigDecimal value" in { - - val defaultGeneratedBigDecimal = nextBigDecimal() - - defaultGeneratedBigDecimal should be >= BigDecimal(0.00) - defaultGeneratedBigDecimal should be <= BigDecimal(1000000.00) - defaultGeneratedBigDecimal.precision should be(2) - - val unitIntervalBigDecimal = nextBigDecimal(1.00, 8) - - unitIntervalBigDecimal should be >= BigDecimal(0.00) - unitIntervalBigDecimal should be <= BigDecimal(1.00) - unitIntervalBigDecimal.precision should be(8) - } - - it should "be able to generate a specific value from a set of values" in { - - val possibleOptions = Set(1, 3, 5, 123, 0, 9) - - val pick1 = generators.oneOf(possibleOptions) - val pick2 = generators.oneOf(possibleOptions) - val pick3 = generators.oneOf(possibleOptions) - - possibleOptions should contain(pick1) - possibleOptions should contain(pick2) - possibleOptions should contain(pick3) - - val pick4 = generators.oneOf(1, 3, 5, 123, 0, 9) - val pick5 = generators.oneOf(1, 3, 5, 123, 0, 9) - val pick6 = generators.oneOf(1, 3, 5, 123, 0, 9) - - possibleOptions should contain(pick4) - possibleOptions should contain(pick5) - possibleOptions should contain(pick6) - - Set(pick1, pick2, pick3, pick4, pick5, pick6).size should be >= 1 - } - - it should "be able to generate array with values generated by generators" in { - - val arrayOfTimes = arrayOf(nextTime(), 16) - arrayOfTimes.length should be <= 16 - - val arrayOfBigDecimals = arrayOf(nextBigDecimal(), 8) - arrayOfBigDecimals.length should be <= 8 - } - - it should "be able to generate seq with values generated by generators" in { - - val seqOfTimes = seqOf(nextTime(), 16) - seqOfTimes.size should be <= 16 - - val seqOfBigDecimals = seqOf(nextBigDecimal(), 8) - seqOfBigDecimals.size should be <= 8 - } - - it should "be able to generate vector with values generated by generators" in { - - val vectorOfTimes = vectorOf(nextTime(), 16) - vectorOfTimes.size should be <= 16 - - val vectorOfStrings = seqOf(nextString(), 8) - vectorOfStrings.size should be <= 8 - } - - it should "be able to generate list with values generated by generators" in { - - val listOfTimes = listOf(nextTime(), 16) - listOfTimes.size should be <= 16 - - val listOfBigDecimals = seqOf(nextBigDecimal(), 8) - listOfBigDecimals.size should be <= 8 - } - - it should "be able to generate set with values generated by generators" in { - - val setOfTimes = vectorOf(nextTime(), 16) - setOfTimes.size should be <= 16 - - val setOfBigDecimals = seqOf(nextBigDecimal(), 8) - setOfBigDecimals.size should be <= 8 - } - - it should "be able to generate maps with keys and values generated by generators" in { - - val generatedConstantMap = mapOf(10, "key", 123) - generatedConstantMap.size should be <= 1 - assert(generatedConstantMap.keys.forall(_ == "key")) - assert(generatedConstantMap.values.forall(_ == 123)) - - val generatedMap = mapOf(10, nextString(10), nextBigDecimal()) - assert(generatedMap.keys.forall(_.length <= 10)) - assert(generatedMap.values.forall(_ >= BigDecimal(0.00))) - } - - it should "compose deeply" in { - - val generatedNestedMap = mapOf(10, nextString(10), nextPair(nextBigDecimal(), nextOption(123))) - - generatedNestedMap.size should be <= 10 - generatedNestedMap.keySet.size should be <= 10 - generatedNestedMap.values.size should be <= 10 - assert(generatedNestedMap.values.forall(value => !value._2.exists(_ != 123))) - } -} diff --git a/src/test/scala/com/drivergrp/core/JsonTest.scala b/src/test/scala/com/drivergrp/core/JsonTest.scala deleted file mode 100644 index 125e97c..0000000 --- a/src/test/scala/com/drivergrp/core/JsonTest.scala +++ /dev/null @@ -1,101 +0,0 @@ -package com.drivergrp.core - -import com.drivergrp.core.json.{EnumJsonFormat, ValueClassFormat} -import com.drivergrp.core.revision.Revision -import com.drivergrp.core.time.provider.SystemTimeProvider -import org.scalatest.{FlatSpec, Matchers} - -class JsonTest extends FlatSpec with Matchers { - - "Json format for Id" should "read and write correct JSON" in { - - val referenceId = Id[String](1312L) - - val writtenJson = com.drivergrp.core.json.idFormat.write(referenceId) - writtenJson.prettyPrint should be("1312") - - val parsedId = com.drivergrp.core.json.idFormat.read(writtenJson) - parsedId should be(referenceId) - } - - "Json format for Name" should "read and write correct JSON" in { - - val referenceName = Name[String]("Homer") - - val writtenJson = com.drivergrp.core.json.nameFormat.write(referenceName) - writtenJson.prettyPrint should be("\"Homer\"") - - val parsedName = com.drivergrp.core.json.nameFormat.read(writtenJson) - parsedName should be(referenceName) - } - - "Json format for Time" should "read and write correct JSON" in { - - val referenceTime = new SystemTimeProvider().currentTime() - - val writtenJson = com.drivergrp.core.json.timeFormat.write(referenceTime) - writtenJson.prettyPrint should be("{\n \"timestamp\": " + referenceTime.millis + "\n}") - - val parsedTime = com.drivergrp.core.json.timeFormat.read(writtenJson) - parsedTime should be(referenceTime) - } - - "Json format for Revision" should "read and write correct JSON" in { - - val referenceRevision = Revision[String]("037e2ec0-8901-44ac-8e53-6d39f6479db4") - - val writtenJson = com.drivergrp.core.json.revisionFormat.write(referenceRevision) - writtenJson.prettyPrint should be("\"" + referenceRevision.id + "\"") - - val parsedRevision = com.drivergrp.core.json.revisionFormat.read(writtenJson) - parsedRevision should be(referenceRevision) - } - - "Json format for Enums" should "read and write correct JSON" in { - - sealed trait EnumVal - case object Val1 extends EnumVal - case object Val2 extends EnumVal - case object Val3 extends EnumVal - - val format = new EnumJsonFormat[EnumVal]("a" -> Val1, "b" -> Val2, "c" -> Val3) - - val referenceEnumValue1 = Val2 - val referenceEnumValue2 = Val3 - - val writtenJson1 = format.write(referenceEnumValue1) - writtenJson1.prettyPrint should be("\"b\"") - - val writtenJson2 = format.write(referenceEnumValue2) - writtenJson2.prettyPrint should be("\"c\"") - - val parsedEnumValue1 = format.read(writtenJson1) - val parsedEnumValue2 = format.read(writtenJson2) - - parsedEnumValue1 should be(referenceEnumValue1) - parsedEnumValue2 should be(referenceEnumValue2) - } - - // Should be defined outside of case to have a TypeTag - case class CustomWrapperClass(value: Int) - - "Json format for Value classes" should "read and write correct JSON" in { - - val format = new ValueClassFormat[CustomWrapperClass](v => BigDecimal(v.value), d => CustomWrapperClass(d.toInt)) - - val referenceValue1 = CustomWrapperClass(-2) - val referenceValue2 = CustomWrapperClass(10) - - val writtenJson1 = format.write(referenceValue1) - writtenJson1.prettyPrint should be("-2") - - val writtenJson2 = format.write(referenceValue2) - writtenJson2.prettyPrint should be("10") - - val parsedValue1 = format.read(writtenJson1) - val parsedValue2 = format.read(writtenJson2) - - parsedValue1 should be(referenceValue1) - parsedValue2 should be(referenceValue2) - } -} diff --git a/src/test/scala/com/drivergrp/core/MessagesTest.scala b/src/test/scala/com/drivergrp/core/MessagesTest.scala deleted file mode 100644 index 21fe30a..0000000 --- a/src/test/scala/com/drivergrp/core/MessagesTest.scala +++ /dev/null @@ -1,80 +0,0 @@ -package com.drivergrp.core - -import java.util.Locale - -import com.drivergrp.core.logging.Logger -import com.drivergrp.core.messages.Messages -import com.typesafe.config.{ConfigException, ConfigFactory} -import org.mockito.Mockito._ -import org.scalatest.mock.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} - -import scala.collection.JavaConversions._ - -class MessagesTest extends FlatSpec with Matchers with MockitoSugar { - - val englishLocaleMessages = - Map("en.greeting" -> "Hello {0}!", "en.greetingFullName" -> "Hello {0} {1} {2}!", "en.hello" -> "Hello world!") - - "Messages" should "read messages from config and format with parameters" in { - - val log = mock[Logger] - val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) - - val messages = Messages.messages(messagesConfig, log, Locale.US) - - messages("hello") should be("Hello world!") - messages("greeting", "Homer") should be("Hello Homer!") - messages("greetingFullName", "Homer", "J", "Simpson") should be("Hello Homer J Simpson!") - } - - it should "be able to read messages for different locales" in { - - val log = mock[Logger] - - val messagesConfig = ConfigFactory.parseMap( - englishLocaleMessages ++ Map( - "zh.hello" -> "你好,世界!", - "zh.greeting" -> "你好,{0}!", - "zh.greetingFullName" -> "你好,{0} {1} {2}!" - )) - - val englishMessages = Messages.messages(messagesConfig, log, Locale.US) - val englishMessagesToo = Messages.messages(messagesConfig, log, Locale.ENGLISH) - val chineseMessages = Messages.messages(messagesConfig, log, Locale.CHINESE) - - englishMessages("hello") should be("Hello world!") - englishMessages("greeting", "Homer") should be("Hello Homer!") - englishMessages("greetingFullName", "Homer", "J", "Simpson") should be("Hello Homer J Simpson!") - - englishMessagesToo("hello") should be(englishMessages("hello")) - englishMessagesToo("greeting", "Homer") should be(englishMessages("greeting", "Homer")) - englishMessagesToo("greetingFullName", "Homer", "J", "Simpson") should be( - englishMessages("greetingFullName", "Homer", "J", "Simpson")) - - chineseMessages("hello") should be("你好,世界!") - chineseMessages("greeting", "Homer") should be("你好,Homer!") - chineseMessages("greetingFullName", "Homer", "J", "Simpson") should be("你好,Homer J Simpson!") - } - - it should "raise exception when locale is not available" in { - - val log = mock[Logger] - val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) - - an[ConfigException.Missing] should be thrownBy - Messages.messages(messagesConfig, log, Locale.GERMAN) - } - - it should "log a problem, when there is no message for key" in { - - val log = mock[Logger] - val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) - - val messages = Messages.messages(messagesConfig, log, Locale.US) - - messages("howdy") should be("howdy") - - verify(log).error(s"Message with key 'howdy' not found for locale 'en'") - } -} diff --git a/src/test/scala/com/drivergrp/core/StatsTest.scala b/src/test/scala/com/drivergrp/core/StatsTest.scala deleted file mode 100644 index c4f449b..0000000 --- a/src/test/scala/com/drivergrp/core/StatsTest.scala +++ /dev/null @@ -1,43 +0,0 @@ -package com.drivergrp.core - -import com.drivergrp.core.logging.Logger -import com.drivergrp.core.stats.LogStats -import com.drivergrp.core.time.{Time, TimeRange} -import org.scalatest.mock.MockitoSugar -import org.scalatest.{FlatSpec, Matchers} -import org.mockito.Mockito._ - -class StatsTest extends FlatSpec with Matchers with MockitoSugar { - - "Stats" should "format and store all recorded data" in { - - val log = mock[Logger] - val stats = new LogStats(log) - - stats.recordStats(Seq(), TimeRange(Time(2L), Time(5L)), BigDecimal(123.324)) - verify(log).audit(s"(2-5)=123.324") - - stats.recordStats("stat", TimeRange(Time(5L), Time(5L)), BigDecimal(333L)) - verify(log).audit(s"stat(5-5)=333") - - stats.recordStats("stat", Time(934L), 123) - verify(log).audit(s"stat(934-934)=123") - - stats.recordStats("stat", Time(0L), 123) - verify(log).audit(s"stat(0-0)=123") - } - - it should "format BigDecimal with all precision digits" in { - - val log = mock[Logger] - val stats = new LogStats(log) - - stats.recordStats(Seq("root", "group", "stat", "substat"), - TimeRange(Time(1467381889834L), Time(1468937089834L)), - BigDecimal(3.333333333333333)) - verify(log).audit(s"root.group.stat.substat(1467381889834-1468937089834)=3.333333333333333") - - stats.recordStats("stat", Time(1233L), BigDecimal(0.00000000000000000000001)) - verify(log).audit(s"stat(1233-1233)=0.000000000000000000000010") - } -} diff --git a/src/test/scala/com/drivergrp/core/TimeTest.scala b/src/test/scala/com/drivergrp/core/TimeTest.scala deleted file mode 100644 index b928413..0000000 --- a/src/test/scala/com/drivergrp/core/TimeTest.scala +++ /dev/null @@ -1,87 +0,0 @@ -package com.drivergrp.core - -import java.util.TimeZone - -import com.drivergrp.core.time.{Time, _} -import org.scalacheck.{Arbitrary, Gen} -import org.scalatest.{FlatSpec, Matchers} -import org.scalatest.prop.Checkers -import org.scalacheck.Arbitrary._ -import org.scalacheck.Prop.BooleanOperators - -import scala.concurrent.duration._ - -class TimeTest extends FlatSpec with Matchers with Checkers { - - implicit val arbitraryDuration = Arbitrary[Duration](Gen.chooseNum(0L, 9999999999L).map(_.milliseconds)) - implicit val arbitraryTime = Arbitrary[Time](Gen.chooseNum(0L, 9999999999L).map(millis => Time(millis))) - - "Time" should "have correct methods to compare" in { - - Time(234L).isAfter(Time(123L)) should be(true) - Time(123L).isAfter(Time(123L)) should be(false) - Time(123L).isAfter(Time(234L)) should be(false) - - check((a: Time, b: Time) => (a.millis > b.millis) ==> a.isAfter(b)) - - Time(234L).isBefore(Time(123L)) should be(false) - Time(123L).isBefore(Time(123L)) should be(false) - Time(123L).isBefore(Time(234L)) should be(true) - - check { (a: Time, b: Time) => - (a.millis < b.millis) ==> a.isBefore(b) - } - } - - it should "not modify time" in { - - Time(234L).millis should be(234L) - - check { millis: Long => - Time(millis).millis == millis - } - } - - it should "support arithmetic with scala.concurrent.duration" in { - - Time(123L).advanceBy(0 minutes).millis should be(123L) - Time(123L).advanceBy(1 second).millis should be(123L + Second) - Time(123L).advanceBy(4 days).millis should be(123L + 4 * Days) - - check { (time: Time, duration: Duration) => - time.advanceBy(duration).millis == (time.millis + duration.toMillis) - } - } - - it should "have ordering defined correctly" in { - - Seq(Time(321L), Time(123L), Time(231L)).sorted should - contain theSameElementsInOrderAs Seq(Time(123L), Time(231L), Time(321L)) - - check { times: List[Time] => - times.sorted.sliding(2).filter(_.size == 2).forall { - case Seq(a, b) => - a.millis <= b.millis - } - } - } - - it should "reset to the start of the period, e.g. month" in { - - startOfMonth(Time(1468937089834L)) should be(Time(1467381889834L)) - startOfMonth(Time(1467381889834L)) should be(Time(1467381889834L)) // idempotent - } - - it should "have correct textual representations" in { - - textualDate(TimeZone.getTimeZone("EDT"))(Time(1468937089834L)) should be("July 19, 2016") - textualTime(TimeZone.getTimeZone("PDT"))(Time(1468937089834L)) should be("Jul 19, 2016 02:04:49 PM") - } - - "TimeRange" should "have duration defined as a difference of start and end times" in { - - TimeRange(Time(321L), Time(432L)).duration should be(111.milliseconds) - TimeRange(Time(432L), Time(321L)).duration should be((-111).milliseconds) - TimeRange(Time(333L), Time(333L)).duration should be(0.milliseconds) - } -} diff --git a/src/test/scala/xyz/driver/core/AuthTest.scala b/src/test/scala/xyz/driver/core/AuthTest.scala new file mode 100644 index 0000000..fef3eda --- /dev/null +++ b/src/test/scala/xyz/driver/core/AuthTest.scala @@ -0,0 +1,77 @@ +package xyz.driver.core + +import akka.http.scaladsl.testkit.ScalatestRouteTest +import akka.http.scaladsl.server._ +import Directives._ +import akka.http.scaladsl.model.headers.{HttpChallenges, RawHeader} +import akka.http.scaladsl.server.AuthenticationFailedRejection.CredentialsRejected +import org.scalatest.mock.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.auth._ + +import scala.concurrent.Future +import scalaz.OptionT + +class AuthTest extends FlatSpec with Matchers with MockitoSugar with ScalatestRouteTest { + + val authStatusService: AuthService[User] = new AuthService[User] { + override def authStatus(authToken: AuthToken): OptionT[Future, User] = OptionT.optionT[Future] { + Future.successful(Some(new User() { + override def id: Id[User] = Id[User](1L) + override def roles: Set[Role] = Set(PathologistRole) + })) + } + } + + import authStatusService._ + + "'authorize' directive" should "throw error is auth token is not in the request" in { + + Get("/naive/attempt") ~> + authorize(CanSignOutReport) { + case (authToken, user) => + complete("Never going to be here") + } ~> + check { + handled shouldBe false + rejections should contain(MissingHeaderRejection("WWW-Authenticate")) + } + } + + it should "throw error is authorized user is not having the requested permission" in { + + val referenceAuthToken = AuthToken(Base64("I am a pathologist's token")) + + Post("/administration/attempt").addHeader( + RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value) + ) ~> + authorize(CanAssignRoles) { + case (authToken, user) => + complete("Never going to get here") + } ~> + check { + handled shouldBe false + rejections should contain( + AuthenticationFailedRejection( + CredentialsRejected, + HttpChallenges.basic("User does not have the required permissions: CanAssignRoles"))) + } + } + + it should "pass and retrieve the token to client code, if token is in request and user has permission" in { + + val referenceAuthToken = AuthToken(Base64("I am token")) + + Get("/valid/attempt/?a=2&b=5").addHeader( + RawHeader(AuthService.AuthenticationTokenHeader, referenceAuthToken.value.value) + ) ~> + authorize(CanSignOutReport) { + case (authToken, user) => + complete("Alright, \"" + authToken.value.value + "\" is handled") + } ~> + check { + handled shouldBe true + responseAs[String] shouldBe "Alright, \"I am token\" is handled" + } + } +} diff --git a/src/test/scala/xyz/driver/core/CoreTest.scala b/src/test/scala/xyz/driver/core/CoreTest.scala new file mode 100644 index 0000000..f9a1aab --- /dev/null +++ b/src/test/scala/xyz/driver/core/CoreTest.scala @@ -0,0 +1,61 @@ +package xyz.driver.core + +import java.io.ByteArrayOutputStream + +import org.mockito.Mockito._ +import org.scalatest.mock.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.revision.Revision + +class CoreTest extends FlatSpec with Matchers with MockitoSugar { + + "'make' function" should "allow initialization for objects" in { + + val createdAndInitializedValue = make(new ByteArrayOutputStream(128)) { baos => + baos.write(Array(1.toByte, 1.toByte, 0.toByte)) + } + + createdAndInitializedValue.toByteArray should be(Array(1.toByte, 1.toByte, 0.toByte)) + } + + "'using' function" should "call close after performing action on resource" in { + + val baos = mock[ByteArrayOutputStream] + + using(baos /* usually new ByteArrayOutputStream(128) */ ) { baos => + baos.write(Array(1.toByte, 1.toByte, 0.toByte)) + } + + verify(baos).close() + } + + "Id" should "have equality and ordering working correctly" in { + + (Id[String](1234213L) === Id[String](1234213L)) should be(true) + (Id[String](1234213L) === Id[String](213414L)) should be(false) + (Id[String](213414L) === Id[String](1234213L)) should be(false) + + Seq(Id[String](4L), Id[String](3L), Id[String](2L), Id[String](1L)).sorted should contain + theSameElementsInOrderAs(Seq(Id[String](1L), Id[String](2L), Id[String](3L), Id[String](4L))) + } + + "Name" should "have equality and ordering working correctly" in { + + (Name[String]("foo") === Name[String]("foo")) should be(true) + (Name[String]("foo") === Name[String]("bar")) should be(false) + (Name[String]("bar") === Name[String]("foo")) should be(false) + + Seq(Name[String]("d"), Name[String]("cc"), Name[String]("a"), Name[String]("bbb")).sorted should contain + theSameElementsInOrderAs(Seq(Name[String]("a"), Name[String]("bbb"), Name[String]("cc"), Name[String]("d"))) + } + + "Revision" should "have equality working correctly" in { + + val bla = Revision[String]("85569dab-a3dc-401b-9f95-d6fb4162674b") + val foo = Revision[String]("f54b3558-bdcd-4646-a14b-8beb11f6b7c4") + + (bla === bla) should be(true) + (bla === foo) should be(false) + (foo === bla) should be(false) + } +} diff --git a/src/test/scala/xyz/driver/core/FileTest.scala b/src/test/scala/xyz/driver/core/FileTest.scala new file mode 100644 index 0000000..aba79f7 --- /dev/null +++ b/src/test/scala/xyz/driver/core/FileTest.scala @@ -0,0 +1,126 @@ +package xyz.driver.core + +import java.io.File +import java.nio.file.Paths + +import com.amazonaws.services.s3.AmazonS3 +import com.amazonaws.services.s3.model._ +import org.mockito.Matchers._ +import org.mockito.Mockito._ +import org.scalatest.mock.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.file.{FileSystemStorage, S3Storage} + +import scala.concurrent.Await +import scala.concurrent.duration._ + +class FileTest extends FlatSpec with Matchers with MockitoSugar { + + "S3 Storage" should "create and download local files and do other operations" in { + import scala.collection.JavaConverters._ + + val tempDir = System.getProperty("java.io.tmpdir") + val sourceTestFile = generateTestLocalFile(tempDir) + val testFileName = "uploadTestFile" + + val randomFolderName = java.util.UUID.randomUUID().toString + val testDirPath = Paths.get(randomFolderName) + val testFilePath = Paths.get(randomFolderName, testFileName) + + val testBucket = Name[Bucket]("IamBucket") + + val s3PutMock = mock[PutObjectResult] + when(s3PutMock.getETag).thenReturn("IAmEtag") + + val s3ObjectSummaryMock = mock[S3ObjectSummary] + when(s3ObjectSummaryMock.getKey).thenReturn(testFileName) + when(s3ObjectSummaryMock.getETag).thenReturn("IAmEtag") + when(s3ObjectSummaryMock.getLastModified).thenReturn(new java.util.Date()) + + val s3ResultsMock = mock[ListObjectsV2Result] + when(s3ResultsMock.getNextContinuationToken).thenReturn("continuationToken") + when(s3ResultsMock.isTruncated).thenReturn(false, // before file created it is empty (zero pages) + true, + false, // after file is uploaded it contains this one file (one page) + false) // after file is deleted it is empty (zero pages) again + when(s3ResultsMock.getObjectSummaries).thenReturn( + // before file created it is empty, `getObjectSummaries` is never called + List[S3ObjectSummary](s3ObjectSummaryMock).asJava, // after file is uploaded it contains this one file + List.empty[S3ObjectSummary].asJava) // after file is deleted it is empty again + + val s3ObjectMetadataMock = mock[ObjectMetadata] + val amazonS3Mock = mock[AmazonS3] + when(amazonS3Mock.listObjectsV2(any[ListObjectsV2Request]())).thenReturn(s3ResultsMock) + when(amazonS3Mock.putObject(testBucket, testFilePath.toString, sourceTestFile)).thenReturn(s3PutMock) + when(amazonS3Mock.getObject(any[GetObjectRequest](), any[File]())).thenReturn(s3ObjectMetadataMock) + + val s3Storage = new S3Storage(amazonS3Mock, testBucket, scala.concurrent.ExecutionContext.global) + + val filesBefore = Await.result(s3Storage.list(testDirPath).run, 10 seconds) + filesBefore shouldBe empty + + Await.result(s3Storage.upload(sourceTestFile, testFilePath), 10 seconds) + + val filesAfterUpload = Await.result(s3Storage.list(testDirPath).run, 10 seconds) + filesAfterUpload.size should be(1) + val uploadedFileLine = filesAfterUpload.head + uploadedFileLine.name should be(Name[File](testFileName)) + uploadedFileLine.location should be(testFilePath) + uploadedFileLine.revision.id.length should be > 0 + uploadedFileLine.lastModificationDate.millis should be > 0L + + val downloadedFile = Await.result(s3Storage.download(testFilePath).run, 10 seconds) + downloadedFile shouldBe defined + downloadedFile.foreach { + _.getAbsolutePath.endsWith(testFilePath.toString) should be(true) + } + + Await.result(s3Storage.delete(testFilePath), 10 seconds) + + val filesAfterRemoval = Await.result(s3Storage.list(testDirPath).run, 10 seconds) + filesAfterRemoval shouldBe empty + } + + "Filesystem files storage" should "create and download local files and do other operations" in { + + val tempDir = System.getProperty("java.io.tmpdir") + val sourceTestFile = generateTestLocalFile(tempDir) + + val randomFolderName = java.util.UUID.randomUUID().toString + val testDirPath = Paths.get(tempDir, randomFolderName) + val testFilePath = Paths.get(tempDir, randomFolderName, "uploadTestFile") + + val fileStorage = new FileSystemStorage(scala.concurrent.ExecutionContext.global) + + val filesBefore = Await.result(fileStorage.list(testDirPath).run, 10 seconds) + filesBefore shouldBe empty + + Await.result(fileStorage.upload(sourceTestFile, testFilePath), 10 seconds) + + val filesAfterUpload = Await.result(fileStorage.list(testDirPath).run, 10 seconds) + filesAfterUpload.size should be(1) + val uploadedFileLine = filesAfterUpload.head + uploadedFileLine.name should be(Name[File]("uploadTestFile")) + uploadedFileLine.location should be(testFilePath) + uploadedFileLine.revision.id.length should be > 0 + uploadedFileLine.lastModificationDate.millis should be > 0L + + val downloadedFile = Await.result(fileStorage.download(testFilePath).run, 10 seconds) + downloadedFile shouldBe defined + downloadedFile.map(_.getAbsolutePath) should be(Some(testFilePath.toString)) + + Await.result(fileStorage.delete(testFilePath), 10 seconds) + + val filesAfterRemoval = Await.result(fileStorage.list(testDirPath).run, 10 seconds) + filesAfterRemoval shouldBe empty + } + + private def generateTestLocalFile(path: String): File = { + val randomSourceFolderName = java.util.UUID.randomUUID().toString + val sourceTestFile = new File(Paths.get(path, randomSourceFolderName, "uploadTestFile").toString) + sourceTestFile.getParentFile.mkdirs() should be(true) + sourceTestFile.createNewFile() should be(true) + using(new java.io.PrintWriter(sourceTestFile)) { _.append("Test File Contents") } + sourceTestFile + } +} diff --git a/src/test/scala/xyz/driver/core/GeneratorsTest.scala b/src/test/scala/xyz/driver/core/GeneratorsTest.scala new file mode 100644 index 0000000..0432b2a --- /dev/null +++ b/src/test/scala/xyz/driver/core/GeneratorsTest.scala @@ -0,0 +1,234 @@ +package xyz.driver.core + +import org.scalatest.{Assertions, FlatSpec, Matchers} + +class GeneratorsTest extends FlatSpec with Matchers with Assertions { + import generators._ + + "Generators" should "be able to generate com.drivergrp.core.Id identifiers" in { + + val generatedId1 = nextId[String]() + val generatedId2 = nextId[String]() + val generatedId3 = nextId[Long]() + + generatedId1 should be >= 0L + generatedId2 should be >= 0L + generatedId3 should be >= 0L + generatedId1 should not be generatedId2 + generatedId2 should !==(generatedId3) + } + + it should "be able to generate com.drivergrp.core.Id identifiers with max value" in { + + val generatedLimitedId1 = nextId[String](10000) + val generatedLimitedId2 = nextId[String](1000) + val generatedLimitedId3 = nextId[Long](2000) + + generatedLimitedId1 should be >= 0L + generatedLimitedId1 should be < 10000L + generatedLimitedId2 should be >= 0L + generatedLimitedId2 should be < 1000L + generatedLimitedId3 should be >= 0L + generatedLimitedId3 should be < 2000L + generatedLimitedId1 should not be generatedLimitedId2 + generatedLimitedId2 should !==(generatedLimitedId3) + } + + it should "be able to generate com.drivergrp.core.Name names" in { + + nextName[String]() should not be nextName[String]() + nextName[String]().length should be >= 0 + + val fixedLengthName = nextName[String](10) + fixedLengthName.length should be <= 10 + assert(!fixedLengthName.exists(_.isControl)) + } + + it should "be able to generate proper UUIDs" in { + + nextUuid() should not be nextUuid() + nextUuid().toString.length should be(36) + } + + it should "be able to generate new Revisions" in { + + nextRevision[String]() should not be nextRevision[String]() + nextRevision[String]().id.length should be > 0 + } + + it should "be able to generate strings" in { + + nextString() should not be nextString() + nextString().length should be >= 0 + + val fixedLengthString = nextString(20) + fixedLengthString.length should be <= 20 + assert(!fixedLengthString.exists(_.isControl)) + } + + it should "be able to generate options which are sometimes have values and sometimes not" in { + + val generatedOption = nextOption("2") + + generatedOption should not contain "1" + assert(generatedOption === Some("2") || generatedOption === None) + } + + it should "be able to generate a pair of two generated values" in { + + val constantPair = nextPair("foo", 1L) + constantPair._1 should be("foo") + constantPair._2 should be(1L) + + val generatedPair = nextPair(nextId[Int](), nextName[Int]()) + + generatedPair._1 should be > 0L + generatedPair._2.length should be > 0 + + nextPair(nextId[Int](), nextName[Int]()) should not be + nextPair(nextId[Int](), nextName[Int]()) + } + + it should "be able to generate a triad of two generated values" in { + + val constantTriad = nextTriad("foo", "bar", 1L) + constantTriad._1 should be("foo") + constantTriad._2 should be("bar") + constantTriad._3 should be(1L) + + val generatedTriad = nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) + + generatedTriad._1 should be > 0L + generatedTriad._2.length should be > 0 + generatedTriad._3 should be >= BigDecimal(0.00) + + nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) should not be + nextTriad(nextId[Int](), nextName[Int](), nextBigDecimal()) + } + + it should "be able to generate a time value" in { + + val generatedTime = nextTime() + val currentTime = System.currentTimeMillis() + + generatedTime.millis should be >= 0L + generatedTime.millis should be <= currentTime + } + + it should "be able to generate a time range value" in { + + val generatedTimeRange = nextTimeRange() + val currentTime = System.currentTimeMillis() + + generatedTimeRange.start.millis should be >= 0L + generatedTimeRange.start.millis should be <= currentTime + generatedTimeRange.end.millis should be >= 0L + generatedTimeRange.end.millis should be <= currentTime + generatedTimeRange.start.millis should be <= generatedTimeRange.end.millis + } + + it should "be able to generate a BigDecimal value" in { + + val defaultGeneratedBigDecimal = nextBigDecimal() + + defaultGeneratedBigDecimal should be >= BigDecimal(0.00) + defaultGeneratedBigDecimal should be <= BigDecimal(1000000.00) + defaultGeneratedBigDecimal.precision should be(2) + + val unitIntervalBigDecimal = nextBigDecimal(1.00, 8) + + unitIntervalBigDecimal should be >= BigDecimal(0.00) + unitIntervalBigDecimal should be <= BigDecimal(1.00) + unitIntervalBigDecimal.precision should be(8) + } + + it should "be able to generate a specific value from a set of values" in { + + val possibleOptions = Set(1, 3, 5, 123, 0, 9) + + val pick1 = generators.oneOf(possibleOptions) + val pick2 = generators.oneOf(possibleOptions) + val pick3 = generators.oneOf(possibleOptions) + + possibleOptions should contain(pick1) + possibleOptions should contain(pick2) + possibleOptions should contain(pick3) + + val pick4 = generators.oneOf(1, 3, 5, 123, 0, 9) + val pick5 = generators.oneOf(1, 3, 5, 123, 0, 9) + val pick6 = generators.oneOf(1, 3, 5, 123, 0, 9) + + possibleOptions should contain(pick4) + possibleOptions should contain(pick5) + possibleOptions should contain(pick6) + + Set(pick1, pick2, pick3, pick4, pick5, pick6).size should be >= 1 + } + + it should "be able to generate array with values generated by generators" in { + + val arrayOfTimes = arrayOf(nextTime(), 16) + arrayOfTimes.length should be <= 16 + + val arrayOfBigDecimals = arrayOf(nextBigDecimal(), 8) + arrayOfBigDecimals.length should be <= 8 + } + + it should "be able to generate seq with values generated by generators" in { + + val seqOfTimes = seqOf(nextTime(), 16) + seqOfTimes.size should be <= 16 + + val seqOfBigDecimals = seqOf(nextBigDecimal(), 8) + seqOfBigDecimals.size should be <= 8 + } + + it should "be able to generate vector with values generated by generators" in { + + val vectorOfTimes = vectorOf(nextTime(), 16) + vectorOfTimes.size should be <= 16 + + val vectorOfStrings = seqOf(nextString(), 8) + vectorOfStrings.size should be <= 8 + } + + it should "be able to generate list with values generated by generators" in { + + val listOfTimes = listOf(nextTime(), 16) + listOfTimes.size should be <= 16 + + val listOfBigDecimals = seqOf(nextBigDecimal(), 8) + listOfBigDecimals.size should be <= 8 + } + + it should "be able to generate set with values generated by generators" in { + + val setOfTimes = vectorOf(nextTime(), 16) + setOfTimes.size should be <= 16 + + val setOfBigDecimals = seqOf(nextBigDecimal(), 8) + setOfBigDecimals.size should be <= 8 + } + + it should "be able to generate maps with keys and values generated by generators" in { + + val generatedConstantMap = mapOf(10, "key", 123) + generatedConstantMap.size should be <= 1 + assert(generatedConstantMap.keys.forall(_ == "key")) + assert(generatedConstantMap.values.forall(_ == 123)) + + val generatedMap = mapOf(10, nextString(10), nextBigDecimal()) + assert(generatedMap.keys.forall(_.length <= 10)) + assert(generatedMap.values.forall(_ >= BigDecimal(0.00))) + } + + it should "compose deeply" in { + + val generatedNestedMap = mapOf(10, nextString(10), nextPair(nextBigDecimal(), nextOption(123))) + + generatedNestedMap.size should be <= 10 + generatedNestedMap.keySet.size should be <= 10 + generatedNestedMap.values.size should be <= 10 + assert(generatedNestedMap.values.forall(value => !value._2.exists(_ != 123))) + } +} diff --git a/src/test/scala/xyz/driver/core/JsonTest.scala b/src/test/scala/xyz/driver/core/JsonTest.scala new file mode 100644 index 0000000..bcdcd5d --- /dev/null +++ b/src/test/scala/xyz/driver/core/JsonTest.scala @@ -0,0 +1,101 @@ +package xyz.driver.core + +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.json.{EnumJsonFormat, ValueClassFormat} +import xyz.driver.core.revision.Revision +import xyz.driver.core.time.provider.SystemTimeProvider + +class JsonTest extends FlatSpec with Matchers { + + "Json format for Id" should "read and write correct JSON" in { + + val referenceId = Id[String](1312L) + + val writtenJson = json.idFormat.write(referenceId) + writtenJson.prettyPrint should be("1312") + + val parsedId = json.idFormat.read(writtenJson) + parsedId should be(referenceId) + } + + "Json format for Name" should "read and write correct JSON" in { + + val referenceName = Name[String]("Homer") + + val writtenJson = json.nameFormat.write(referenceName) + writtenJson.prettyPrint should be("\"Homer\"") + + val parsedName = json.nameFormat.read(writtenJson) + parsedName should be(referenceName) + } + + "Json format for Time" should "read and write correct JSON" in { + + val referenceTime = new SystemTimeProvider().currentTime() + + val writtenJson = json.timeFormat.write(referenceTime) + writtenJson.prettyPrint should be("{\n \"timestamp\": " + referenceTime.millis + "\n}") + + val parsedTime = json.timeFormat.read(writtenJson) + parsedTime should be(referenceTime) + } + + "Json format for Revision" should "read and write correct JSON" in { + + val referenceRevision = Revision[String]("037e2ec0-8901-44ac-8e53-6d39f6479db4") + + val writtenJson = json.revisionFormat.write(referenceRevision) + writtenJson.prettyPrint should be("\"" + referenceRevision.id + "\"") + + val parsedRevision = json.revisionFormat.read(writtenJson) + parsedRevision should be(referenceRevision) + } + + "Json format for Enums" should "read and write correct JSON" in { + + sealed trait EnumVal + case object Val1 extends EnumVal + case object Val2 extends EnumVal + case object Val3 extends EnumVal + + val format = new EnumJsonFormat[EnumVal]("a" -> Val1, "b" -> Val2, "c" -> Val3) + + val referenceEnumValue1 = Val2 + val referenceEnumValue2 = Val3 + + val writtenJson1 = format.write(referenceEnumValue1) + writtenJson1.prettyPrint should be("\"b\"") + + val writtenJson2 = format.write(referenceEnumValue2) + writtenJson2.prettyPrint should be("\"c\"") + + val parsedEnumValue1 = format.read(writtenJson1) + val parsedEnumValue2 = format.read(writtenJson2) + + parsedEnumValue1 should be(referenceEnumValue1) + parsedEnumValue2 should be(referenceEnumValue2) + } + + // Should be defined outside of case to have a TypeTag + case class CustomWrapperClass(value: Int) + + "Json format for Value classes" should "read and write correct JSON" in { + + val format = new ValueClassFormat[CustomWrapperClass](v => BigDecimal(v.value), d => CustomWrapperClass(d.toInt)) + + val referenceValue1 = CustomWrapperClass(-2) + val referenceValue2 = CustomWrapperClass(10) + + val writtenJson1 = format.write(referenceValue1) + writtenJson1.prettyPrint should be("-2") + + val writtenJson2 = format.write(referenceValue2) + writtenJson2.prettyPrint should be("10") + + val parsedValue1 = format.read(writtenJson1) + val parsedValue2 = format.read(writtenJson2) + + parsedValue1 should be(referenceValue1) + parsedValue2 should be(referenceValue2) + } +} diff --git a/src/test/scala/xyz/driver/core/MessagesTest.scala b/src/test/scala/xyz/driver/core/MessagesTest.scala new file mode 100644 index 0000000..dc44ee1 --- /dev/null +++ b/src/test/scala/xyz/driver/core/MessagesTest.scala @@ -0,0 +1,80 @@ +package xyz.driver.core + +import java.util.Locale + +import com.typesafe.config.{ConfigException, ConfigFactory} +import org.mockito.Mockito._ +import org.scalatest.mock.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.logging.Logger +import xyz.driver.core.messages.Messages + +import scala.collection.JavaConversions._ + +class MessagesTest extends FlatSpec with Matchers with MockitoSugar { + + val englishLocaleMessages = + Map("en.greeting" -> "Hello {0}!", "en.greetingFullName" -> "Hello {0} {1} {2}!", "en.hello" -> "Hello world!") + + "Messages" should "read messages from config and format with parameters" in { + + val log = mock[Logger] + val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) + + val messages = Messages.messages(messagesConfig, log, Locale.US) + + messages("hello") should be("Hello world!") + messages("greeting", "Homer") should be("Hello Homer!") + messages("greetingFullName", "Homer", "J", "Simpson") should be("Hello Homer J Simpson!") + } + + it should "be able to read messages for different locales" in { + + val log = mock[Logger] + + val messagesConfig = ConfigFactory.parseMap( + englishLocaleMessages ++ Map( + "zh.hello" -> "你好,世界!", + "zh.greeting" -> "你好,{0}!", + "zh.greetingFullName" -> "你好,{0} {1} {2}!" + )) + + val englishMessages = Messages.messages(messagesConfig, log, Locale.US) + val englishMessagesToo = Messages.messages(messagesConfig, log, Locale.ENGLISH) + val chineseMessages = Messages.messages(messagesConfig, log, Locale.CHINESE) + + englishMessages("hello") should be("Hello world!") + englishMessages("greeting", "Homer") should be("Hello Homer!") + englishMessages("greetingFullName", "Homer", "J", "Simpson") should be("Hello Homer J Simpson!") + + englishMessagesToo("hello") should be(englishMessages("hello")) + englishMessagesToo("greeting", "Homer") should be(englishMessages("greeting", "Homer")) + englishMessagesToo("greetingFullName", "Homer", "J", "Simpson") should be( + englishMessages("greetingFullName", "Homer", "J", "Simpson")) + + chineseMessages("hello") should be("你好,世界!") + chineseMessages("greeting", "Homer") should be("你好,Homer!") + chineseMessages("greetingFullName", "Homer", "J", "Simpson") should be("你好,Homer J Simpson!") + } + + it should "raise exception when locale is not available" in { + + val log = mock[Logger] + val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) + + an[ConfigException.Missing] should be thrownBy + Messages.messages(messagesConfig, log, Locale.GERMAN) + } + + it should "log a problem, when there is no message for key" in { + + val log = mock[Logger] + val messagesConfig = ConfigFactory.parseMap(englishLocaleMessages) + + val messages = Messages.messages(messagesConfig, log, Locale.US) + + messages("howdy") should be("howdy") + + verify(log).error(s"Message with key 'howdy' not found for locale 'en'") + } +} diff --git a/src/test/scala/xyz/driver/core/StatsTest.scala b/src/test/scala/xyz/driver/core/StatsTest.scala new file mode 100644 index 0000000..27ea1bd --- /dev/null +++ b/src/test/scala/xyz/driver/core/StatsTest.scala @@ -0,0 +1,43 @@ +package xyz.driver.core + +import org.mockito.Mockito._ +import org.scalatest.mock.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.logging.Logger +import xyz.driver.core.stats.LogStats +import xyz.driver.core.time.{Time, TimeRange} + +class StatsTest extends FlatSpec with Matchers with MockitoSugar { + + "Stats" should "format and store all recorded data" in { + + val log = mock[Logger] + val stats = new LogStats(log) + + stats.recordStats(Seq(), TimeRange(Time(2L), Time(5L)), BigDecimal(123.324)) + verify(log).audit(s"(2-5)=123.324") + + stats.recordStats("stat", TimeRange(Time(5L), Time(5L)), BigDecimal(333L)) + verify(log).audit(s"stat(5-5)=333") + + stats.recordStats("stat", Time(934L), 123) + verify(log).audit(s"stat(934-934)=123") + + stats.recordStats("stat", Time(0L), 123) + verify(log).audit(s"stat(0-0)=123") + } + + it should "format BigDecimal with all precision digits" in { + + val log = mock[Logger] + val stats = new LogStats(log) + + stats.recordStats(Seq("root", "group", "stat", "substat"), + TimeRange(Time(1467381889834L), Time(1468937089834L)), + BigDecimal(3.333333333333333)) + verify(log).audit(s"root.group.stat.substat(1467381889834-1468937089834)=3.333333333333333") + + stats.recordStats("stat", Time(1233L), BigDecimal(0.00000000000000000000001)) + verify(log).audit(s"stat(1233-1233)=0.000000000000000000000010") + } +} diff --git a/src/test/scala/xyz/driver/core/TimeTest.scala b/src/test/scala/xyz/driver/core/TimeTest.scala new file mode 100644 index 0000000..76ef42c --- /dev/null +++ b/src/test/scala/xyz/driver/core/TimeTest.scala @@ -0,0 +1,87 @@ +package xyz.driver.core + +import java.util.TimeZone + +import org.scalacheck.Arbitrary._ +import org.scalacheck.Prop.BooleanOperators +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.prop.Checkers +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.time.{Time, _} + +import scala.concurrent.duration._ + +class TimeTest extends FlatSpec with Matchers with Checkers { + + implicit val arbitraryDuration = Arbitrary[Duration](Gen.chooseNum(0L, 9999999999L).map(_.milliseconds)) + implicit val arbitraryTime = Arbitrary[Time](Gen.chooseNum(0L, 9999999999L).map(millis => Time(millis))) + + "Time" should "have correct methods to compare" in { + + Time(234L).isAfter(Time(123L)) should be(true) + Time(123L).isAfter(Time(123L)) should be(false) + Time(123L).isAfter(Time(234L)) should be(false) + + check((a: Time, b: Time) => (a.millis > b.millis) ==> a.isAfter(b)) + + Time(234L).isBefore(Time(123L)) should be(false) + Time(123L).isBefore(Time(123L)) should be(false) + Time(123L).isBefore(Time(234L)) should be(true) + + check { (a: Time, b: Time) => + (a.millis < b.millis) ==> a.isBefore(b) + } + } + + it should "not modify time" in { + + Time(234L).millis should be(234L) + + check { millis: Long => + Time(millis).millis == millis + } + } + + it should "support arithmetic with scala.concurrent.duration" in { + + Time(123L).advanceBy(0 minutes).millis should be(123L) + Time(123L).advanceBy(1 second).millis should be(123L + Second) + Time(123L).advanceBy(4 days).millis should be(123L + 4 * Days) + + check { (time: Time, duration: Duration) => + time.advanceBy(duration).millis == (time.millis + duration.toMillis) + } + } + + it should "have ordering defined correctly" in { + + Seq(Time(321L), Time(123L), Time(231L)).sorted should + contain theSameElementsInOrderAs Seq(Time(123L), Time(231L), Time(321L)) + + check { times: List[Time] => + times.sorted.sliding(2).filter(_.size == 2).forall { + case Seq(a, b) => + a.millis <= b.millis + } + } + } + + it should "reset to the start of the period, e.g. month" in { + + startOfMonth(Time(1468937089834L)) should be(Time(1467381889834L)) + startOfMonth(Time(1467381889834L)) should be(Time(1467381889834L)) // idempotent + } + + it should "have correct textual representations" in { + + textualDate(TimeZone.getTimeZone("EDT"))(Time(1468937089834L)) should be("July 19, 2016") + textualTime(TimeZone.getTimeZone("PDT"))(Time(1468937089834L)) should be("Jul 19, 2016 02:04:49 PM") + } + + "TimeRange" should "have duration defined as a difference of start and end times" in { + + TimeRange(Time(321L), Time(432L)).duration should be(111.milliseconds) + TimeRange(Time(432L), Time(321L)).duration should be((-111).milliseconds) + TimeRange(Time(333L), Time(333L)).duration should be(0.milliseconds) + } +} -- cgit v1.2.3