Skip to content

Commit

Permalink
Split http4s-laws package off http4s-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
rossabaker committed Jun 17, 2019
1 parent 527507e commit d87fee0
Show file tree
Hide file tree
Showing 26 changed files with 291 additions and 185 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,19 @@ package booPickle

import boopickle.Default._
import cats.effect.IO
import cats.implicits._
import cats.Eq
import cats.effect.laws.util.TestContext
import cats.effect.laws.util.TestInstances._
import org.http4s.headers.`Content-Type`
import org.http4s.testing.EntityCodecTests
import org.http4s.laws.discipline.EntityCodecTests
import org.http4s.MediaType
import org.scalacheck.Arbitrary
import org.scalacheck.Gen

class BoopickleSpec extends Http4sSpec with BooPickleInstances {
implicit val testContext = TestContext()

trait Fruit {
val weight: Double
def color: String
Expand Down Expand Up @@ -44,8 +48,6 @@ class BoopickleSpec extends Http4sSpec with BooPickleInstances {

implicit val fruitEq: Eq[Fruit] = Eq.fromUniversalEquals

implicit val testContext = TestContext()

"boopickle encoder" should {
"have octet-stream content type" in {
encoder.headers.get(`Content-Type`) must_== Some(
Expand Down
13 changes: 12 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Global / cancelable := true

lazy val modules: List[ProjectReference] = List(
core,
laws,
testing,
tests,
server,
Expand Down Expand Up @@ -88,6 +89,16 @@ lazy val core = libraryProject("core")
},
)

lazy val laws = libraryProject("laws")
.settings(
description := "Instances and laws for testing http4s code",
libraryDependencies ++= Seq(
catsEffectLaws,
scalacheck,
),
)
.dependsOn(core)

lazy val testing = libraryProject("testing")
.settings(
description := "Instances and laws for testing http4s code",
Expand All @@ -97,7 +108,7 @@ lazy val testing = libraryProject("testing")
specs2Matcher
),
)
.dependsOn(core)
.dependsOn(laws)

// Defined outside core/src/test so it can depend on published testing
lazy val tests = libraryProject("tests")
Expand Down
6 changes: 3 additions & 3 deletions circe/src/test/scala/org/http4s/circe/CirceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package circe.test // Get out of circe package so we can import custom instances

import cats.effect.IO
import cats.effect.laws.util.TestContext
import cats.syntax.applicative._
import cats.syntax.foldable._
import cats.effect.laws.util.TestInstances._
import cats.implicits._
import io.circe._
import io.circe.syntax._
import io.circe.testing.instances._
Expand All @@ -14,7 +14,7 @@ import org.http4s.Status.Ok
import org.http4s.circe._
import org.http4s.headers.`Content-Type`
import org.http4s.jawn.JawnDecodeSupportSpec
import org.http4s.testing.EntityCodecTests
import org.http4s.laws.discipline.EntityCodecTests
import org.specs2.specification.core.Fragment

// Originally based on ArgonautSpec
Expand Down
31 changes: 31 additions & 0 deletions laws/src/main/scala/org/http4s/laws/EntityCodecLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.http4s
package laws

import cats.implicits._
import cats.effect._
import cats.effect.implicits._
import cats.laws._

trait EntityCodecLaws[F[_], A] extends EntityEncoderLaws[F, A] {
implicit def F: Effect[F]
implicit def encoder: EntityEncoder[F, A]
implicit def decoder: EntityDecoder[F, A]

def entityCodecRoundTrip(a: A): IsEq[IO[Either[DecodeFailure, A]]] =
(for {
entity <- F.delay(encoder.toEntity(a))
message = Request(body = entity.body, headers = encoder.headers)
a0 <- decoder.decode(message, strict = true).value
} yield a0).toIO <-> IO.pure(Right(a))
}

object EntityCodecLaws {
def apply[F[_], A](
implicit F0: Effect[F],
entityEncoderFA: EntityEncoder[F, A],
entityDecoderFA: EntityDecoder[F, A]): EntityCodecLaws[F, A] = new EntityCodecLaws[F, A] {
val F = F0
val encoder = entityEncoderFA
val decoder = entityDecoderFA
}
}
37 changes: 37 additions & 0 deletions laws/src/main/scala/org/http4s/laws/EntityEncoderLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.http4s
package laws

import cats.implicits._
import cats.effect._
import cats.laws._
import org.http4s.headers.{`Content-Length`, `Transfer-Encoding`}

trait EntityEncoderLaws[F[_], A] {
implicit def F: Sync[F]

implicit def encoder: EntityEncoder[F, A]

def accurateContentLengthIfDefined(a: A): IsEq[F[Boolean]] =
(for {
entity <- F.pure(encoder.toEntity(a))
body <- entity.body.compile.toVector
bodyLength = body.size.toLong
contentLength = entity.length
} yield contentLength.fold(true)(_ === bodyLength)) <-> F.pure(true)

def noContentLengthInStaticHeaders: Boolean =
encoder.headers.get(`Content-Length`).isEmpty

def noTransferEncodingInStaticHeaders: Boolean =
encoder.headers.get(`Transfer-Encoding`).isEmpty
}

object EntityEncoderLaws {
def apply[F[_], A](
implicit F0: Sync[F],
entityEncoderFA: EntityEncoder[F, A]
): EntityEncoderLaws[F, A] = new EntityEncoderLaws[F, A] {
val F = F0
val encoder = entityEncoderFA
}
}
18 changes: 18 additions & 0 deletions laws/src/main/scala/org/http4s/laws/HttpCodecLaws.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.http4s
package laws

import cats.laws._
import org.http4s.util.Renderer

trait HttpCodecLaws[A] {
implicit def C: HttpCodec[A]

def httpCodecRoundTrip(a: A): IsEq[ParseResult[A]] =
C.parse(Renderer.renderString(a)) <-> Right(a)
}

object HttpCodecLaws {
def apply[A](implicit httpCodecA: HttpCodec[A]): HttpCodecLaws[A] = new HttpCodecLaws[A] {
val C = httpCodecA
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.http4s.laws.discipline

object arbitrary extends ArbitraryInstances
Loading

0 comments on commit d87fee0

Please sign in to comment.