diff options
Diffstat (limited to 'core-types/src/test/scala')
5 files changed, 464 insertions, 0 deletions
diff --git a/core-types/src/test/scala/xyz/driver/core/CoreTest.scala b/core-types/src/test/scala/xyz/driver/core/CoreTest.scala new file mode 100644 index 0000000..f448d24 --- /dev/null +++ b/core-types/src/test/scala/xyz/driver/core/CoreTest.scala @@ -0,0 +1,88 @@ +package xyz.driver.core + +import java.io.ByteArrayOutputStream + +import org.mockito.Mockito._ +import org.scalatest.mockito.MockitoSugar +import org.scalatest.{FlatSpec, Matchers} + +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]("1234213") === Id[String]("1234213")) should be(true) + (Id[String]("1234213") === Id[String]("213414")) should be(false) + (Id[String]("213414") === Id[String]("1234213")) should be(false) + + val ids = Seq(Id[String]("4"), Id[String]("3"), Id[String]("2"), Id[String]("1")) + val sorted = Seq(Id[String]("1"), Id[String]("2"), Id[String]("3"), Id[String]("4")) + + ids.sorted should contain theSameElementsInOrderAs sorted + } + + it should "have type-safe conversions" in { + final case class X(id: Id[X]) + final case class Y(id: Id[Y]) + final case class Z(id: Id[Z]) + + implicit val xy = Id.Mapper[X, Y] + implicit val yz = Id.Mapper[Y, Z] + + // Test that implicit conversions work correctly + val x = X(Id("0")) + val y = Y(x.id) + val z = Z(y.id) + val y2 = Y(z.id) + val x2 = X(y2.id) + (x2 === x) should be(true) + (y2 === y) should be(true) + + // Test that type inferrence for explicit conversions work correctly + val yid = y.id + val xid = xy(yid) + val zid = yz(yid) + (xid: Id[X]) should be(zid: Id[Z]) + } + + "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) + + val names = Seq(Name[String]("d"), Name[String]("cc"), Name[String]("a"), Name[String]("bbb")) + val sorted = Seq(Name[String]("a"), Name[String]("bbb"), Name[String]("cc"), Name[String]("d")) + names.sorted should contain theSameElementsInOrderAs sorted + } + + "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/core-types/src/test/scala/xyz/driver/core/DateTest.scala b/core-types/src/test/scala/xyz/driver/core/DateTest.scala new file mode 100644 index 0000000..0432040 --- /dev/null +++ b/core-types/src/test/scala/xyz/driver/core/DateTest.scala @@ -0,0 +1,53 @@ +package xyz.driver.core + +import org.scalacheck.{Arbitrary, Gen} +import org.scalatest.prop.Checkers +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.date.Date + +class DateTest extends FlatSpec with Matchers with Checkers { + val dateGenerator = for { + year <- Gen.choose(0, 3000) + month <- Gen.choose(0, 11) + day <- Gen.choose(1, 31) + } yield Date(year, date.Month(month), day) + implicit val arbitraryDate = Arbitrary[Date](dateGenerator) + + "Date" should "correctly convert to and from String" in { + + import xyz.driver.core.generators.nextDate + import date._ + + for (date <- 1 to 100 map (_ => nextDate())) { + Some(date) should be(Date.fromString(date.toString)) + } + } + + it should "have ordering defined correctly" in { + Seq( + Date.fromString("2013-05-10"), + Date.fromString("2020-02-15"), + Date.fromString("2017-03-05"), + Date.fromString("2013-05-12")).sorted should + contain theSameElementsInOrderAs Seq( + Date.fromString("2013-05-10"), + Date.fromString("2013-05-12"), + Date.fromString("2017-03-05"), + Date.fromString("2020-02-15")) + + check { dates: List[Date] => + dates.sorted.sliding(2).filter(_.size == 2).forall { + case Seq(a, b) => + if (a.year == b.year) { + if (a.month == b.month) { + a.day <= b.day + } else { + a.month < b.month + } + } else { + a.year < b.year + } + } + } + } +} diff --git a/core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala b/core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala new file mode 100644 index 0000000..729302b --- /dev/null +++ b/core-types/src/test/scala/xyz/driver/core/PhoneNumberTest.scala @@ -0,0 +1,117 @@ +package xyz.driver.core + +import org.scalatest.{FlatSpec, Matchers} +import xyz.driver.core.domain.PhoneNumber + +class PhoneNumberTest extends FlatSpec with Matchers { + + "PhoneNumber.parse" should "recognize US numbers in international format, ignoring non-digits" in { + // format: off + val numbers = List( + "+18005252225", + "+1 800 525 2225", + "+1 (800) 525-2225", + "+1.800.525.2225") + // format: on + + val parsed = numbers.flatMap(PhoneNumber.parse) + + parsed should have size numbers.size + parsed should contain only PhoneNumber("1", "8005252225") + } + + it should "recognize US numbers without the plus sign" in { + PhoneNumber.parse("18005252225") shouldBe Some(PhoneNumber("1", "8005252225")) + } + + it should "recognize US numbers without country code" in { + // format: off + val numbers = List( + "8005252225", + "800 525 2225", + "(800) 525-2225", + "800.525.2225") + // format: on + + val parsed = numbers.flatMap(PhoneNumber.parse) + + parsed should have size numbers.size + parsed should contain only PhoneNumber("1", "8005252225") + } + + it should "recognize CN numbers in international format" in { + PhoneNumber.parse("+868005252225") shouldBe Some(PhoneNumber("86", "8005252225")) + PhoneNumber.parse("+86 134 52 52 2256") shouldBe Some(PhoneNumber("86", "13452522256")) + } + + it should "parse numbers with extensions in different formats" in { + // format: off + val numbers = List( + "+1 800 525 22 25 x23", + "+18005252225 ext. 23", + "+18005252225,23" + ) + // format: on + + val parsed = numbers.flatMap(PhoneNumber.parse) + + parsed should have size numbers.size + parsed should contain only PhoneNumber("1", "8005252225", Some("23")) + } + + it should "return None on numbers that are shorter than the minimum number of digits for the country (i.e. US - 10, AR - 11)" in { + withClue("US and CN numbers are 10 digits - 9 digit (and shorter) numbers should not fit") { + // format: off + val numbers = List( + "+1 800 525-222", + "+1 800 525-2", + "+86 800 525-222", + "+86 800 525-2") + // format: on + + numbers.flatMap(PhoneNumber.parse) shouldBe empty + } + + withClue("Argentinian numbers are 11 digits (when prefixed with 0) - 10 digit numbers shouldn't fit") { + // format: off + val numbers = List( + "+54 011 525-22256", + "+54 011 525-2225", + "+54 011 525-222") + // format: on + + numbers.flatMap(PhoneNumber.parse) should contain theSameElementsAs List(PhoneNumber("54", "1152522256")) + } + } + + it should "return None on numbers that are longer than the maximum number of digits for the country (i.e. DK - 8, CN - 11)" in { + val numbers = List("+45 27 45 25 22", "+45 135 525 223", "+86 134 525 22256", "+86 135 525 22256 7") + + numbers.flatMap(PhoneNumber.parse) should contain theSameElementsAs + List(PhoneNumber("45", "27452522"), PhoneNumber("86", "13452522256")) + } + + "PhoneNumber.toCompactString/toE164String" should "produce phone number in international format without whitespaces" in { + PhoneNumber.parse("+1 800 5252225").get.toCompactString shouldBe "+18005252225" + PhoneNumber.parse("+1 800 5252225").get.toE164String shouldBe "+18005252225" + + PhoneNumber.parse("+1 800 5252225 x23").get.toCompactString shouldBe "+18005252225;ext=23" + PhoneNumber.parse("+1 800 5252225 x23").get.toE164String shouldBe "+18005252225;ext=23" + } + + "PhoneNumber.toHumanReadableString" should "produce nice readable result for different countries" in { + PhoneNumber.parse("+14154234567").get.toHumanReadableString shouldBe "+1 415-423-4567" + PhoneNumber.parse("+14154234567,23").get.toHumanReadableString shouldBe "+1 415-423-4567 ext. 23" + + PhoneNumber.parse("+78005252225").get.toHumanReadableString shouldBe "+7 800 525-22-25" + + PhoneNumber.parse("+41219437898").get.toHumanReadableString shouldBe "+41 21 943 78 98" + } + + it should "throw an IllegalArgumentException if the PhoneNumber object is not parsable/valid" in { + intercept[IllegalStateException] { + PhoneNumber("+123", "1238123120938120938").toHumanReadableString + }.getMessage should include("+123 1238123120938120938 is not a valid number") + } + +} diff --git a/core-types/src/test/scala/xyz/driver/core/TimeTest.scala b/core-types/src/test/scala/xyz/driver/core/TimeTest.scala new file mode 100644 index 0000000..1019f60 --- /dev/null +++ b/core-types/src/test/scala/xyz/driver/core/TimeTest.scala @@ -0,0 +1,143 @@ +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.date.Month +import xyz.driver.core.time.{Time, _} + +import scala.concurrent.duration._ +import scala.language.postfixOps + +class TimeTest extends FlatSpec with Matchers with Checkers { + + implicit val arbDuration = Arbitrary[Duration](Gen.chooseNum(0L, 9999999999L).map(_.milliseconds)) + implicit val arbTime = 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 { + import java.util.Locale + import java.util.Locale._ + Locale.setDefault(US) + + 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) + } + + "Time" should "use TimeZone correctly when converting to Date" in { + + val EST = java.util.TimeZone.getTimeZone("EST") + val PST = java.util.TimeZone.getTimeZone("PST") + + val timestamp = { + import java.util.Calendar + val cal = Calendar.getInstance(EST) + cal.set(Calendar.HOUR_OF_DAY, 1) + Time(cal.getTime().getTime()) + } + + textualDate(EST)(timestamp) should not be textualDate(PST)(timestamp) + timestamp.toDate(EST) should not be timestamp.toDate(PST) + } + + "TimeOfDay" should "be created from valid strings and convert to java.sql.Time" in { + val s = "07:30:45" + val defaultTimeZone = TimeZone.getDefault() + val todFactory = TimeOfDay.parseTimeString(defaultTimeZone)(_) + val tod = todFactory(s) + tod.timeString shouldBe s + tod.timeZoneString shouldBe defaultTimeZone.getID + val sqlTime = tod.toTime + sqlTime.toLocalTime shouldBe tod.localTime + a[java.time.format.DateTimeParseException] should be thrownBy { + val illegal = "7:15" + todFactory(illegal) + } + } + + "TimeOfDay" should "have correct temporal relationships" in { + val s = "07:30:45" + val t = "09:30:45" + val pst = TimeZone.getTimeZone("America/Los_Angeles") + val est = TimeZone.getTimeZone("America/New_York") + val pstTodFactory = TimeOfDay.parseTimeString(pst)(_) + val estTodFactory = TimeOfDay.parseTimeString(est)(_) + val day = 1 + val month = Month.JANUARY + val year = 2018 + val sTodPst = pstTodFactory(s) + val sTodPst2 = pstTodFactory(s) + val tTodPst = pstTodFactory(t) + val tTodEst = estTodFactory(t) + sTodPst.isBefore(tTodPst, day, month, year) shouldBe true + tTodPst.isAfter(sTodPst, day, month, year) shouldBe true + tTodEst.isBefore(sTodPst, day, month, year) shouldBe true + sTodPst.sameTimeAs(sTodPst2, day, month, year) shouldBe true + } +} diff --git a/core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala b/core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala new file mode 100644 index 0000000..14dfaf9 --- /dev/null +++ b/core-types/src/test/scala/xyz/driver/core/tagging/TaggingTest.scala @@ -0,0 +1,63 @@ +package xyz.driver.core.tagging + +import org.scalatest.{Matchers, WordSpec} +import xyz.driver.core.{@@, Name} + +/** + * @author sergey + * @since 9/11/18 + */ +class TaggingTest extends WordSpec with Matchers { + + "@@ Trimmed" should { + "produce values transparently from Strings and Names (by default)" in { + val s: String @@ Trimmed = " trimmed " + val n: Name[Int] @@ Trimmed = Name(" trimmed ") + + s shouldBe "trimmed" + n shouldBe Name[Int]("trimmed") + } + + "produce values transparently from values that have an implicit conversion defined" in { + import scala.language.implicitConversions + implicit def stringSeq2Trimmed(stringSeq: Seq[String]): Seq[String] @@ Trimmed = + stringSeq.map(_.trim()).tagged[Trimmed] + + val strings: Seq[String] @@ Trimmed = Seq(" trimmed1 ", " trimmed2 ") + strings shouldBe Seq("trimmed1", "trimmed2") + } + + "produce values transparently from Options of values that have Trimmed implicits" in { + val maybeStringDirect: Option[String @@ Trimmed] = Some(" trimmed ") + val maybeStringFromMap: Option[String @@ Trimmed] = Map("s" -> " trimmed ").get("s") + + val maybeNameDirect: Option[Name[Int] @@ Trimmed] = Some(Name(" trimmed ")) + val maybeNameFromMap: Option[Name[Int] @@ Trimmed] = Map("s" -> Name[Int](" trimmed ")).get("s") + + maybeStringDirect shouldBe Some("trimmed") + maybeStringFromMap shouldBe Some("trimmed") + maybeNameDirect shouldBe Some(Name[Int]("trimmed")) + maybeNameFromMap shouldBe Some(Name[Int]("trimmed")) + } + + "produce values transparently from collections of values that have Trimmed implicits" in { + val strings = Seq("s" -> " trimmed1 ", "s" -> " trimmed2 ") + val names = strings.map { + case (k, v) => k -> Name[Int](v) + } + + val trimmedStrings: Seq[String @@ Trimmed] = strings.groupBy(_._1)("s").map(_._2) + val trimmedNames: Seq[Name[Int] @@ Trimmed] = names.groupBy(_._1)("s").map(_._2) + + trimmedStrings shouldBe Seq("trimmed1", "trimmed2") + trimmedNames shouldBe Seq("trimmed1", "trimmed2").map(Name[Int]) + } + + "have Ordering" in { + val names: Seq[Name[Int] @@ Trimmed] = Seq(" 2 ", " 1 ", "3").map(Name[Int]) + + names.sorted should contain inOrderOnly (Name("1"), Name("2"), Name("3")) + } + } + +} |