aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpClientHttpTest.scala12
-rw-r--r--akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpStreamingTests.scala (renamed from tests/src/test/scala/com/softwaremill/sttp/streaming/AkkaHttpStreamingTests.scala)21
-rw-r--r--async-http-client-backend/cats/src/test/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHttpTest.scala12
-rw-r--r--async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpStreamingTest.scala40
-rw-r--r--async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpTest.scala15
-rw-r--r--async-http-client-backend/future/src/test/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHttpTest.scala12
-rw-r--r--async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHttpTest.scala15
-rw-r--r--async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixStreamingTest.scala26
-rw-r--r--async-http-client-backend/scalaz/src/test/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHttpTest.scala13
-rw-r--r--build.sbt60
-rw-r--r--core/src/main/scala/com/softwaremill/sttp/MonadError.scala9
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/ConvertToFuture.scala (renamed from core/src/test/scala/com/softwaremill/sttp/testing/streaming/ConvertToFuture.scala)2
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/CustomMatchers.scala22
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/ForceWrapped.scala30
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala526
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/streaming/StreamingTest.scala65
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/streaming/TestStreamingBackend.scala1
-rw-r--r--implementations/cats/src/test/scala/com/softwaremill/sttp/impl/cats/package.scala2
-rw-r--r--implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/MonixTestStreamingBackend.scala22
-rw-r--r--implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/package.scala2
-rw-r--r--implementations/scalaz/src/test/scala/com/softwaremill/sttp/impl/scalaz/package.scala2
-rw-r--r--okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHttpTest.scala12
-rw-r--r--okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixStreamingTest.scala26
-rw-r--r--okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpFutureHttpTest.scala12
-rw-r--r--okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpSyncHttpTest.scala10
-rw-r--r--project/PollingUtils.scala45
-rw-r--r--project/plugins.sbt7
-rw-r--r--test-server/src/main/resources/binaryfile.jpgbin0 -> 42010 bytes
-rw-r--r--test-server/src/main/resources/textfile.txt100
-rw-r--r--test-server/src/main/scala/com/softwaremill/sttp/server/TestHttpServer.scala197
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala702
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/EvalScala.scala11
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/HttpURLConnectionHttpTest.scala9
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/StreamingTests.scala87
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/TryHttpURLConnectionHttpTest.scala11
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientFs2StreamingTests.scala30
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientMonixStreamingTests.scala18
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/streaming/OkHttpMonixStreamingTests.scala18
-rw-r--r--tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala82
39 files changed, 1318 insertions, 968 deletions
diff --git a/akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpClientHttpTest.scala b/akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpClientHttpTest.scala
new file mode 100644
index 0000000..fe72794
--- /dev/null
+++ b/akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpClientHttpTest.scala
@@ -0,0 +1,12 @@
+package com.softwaremill.sttp.akkahttp
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scala.concurrent.Future
+
+class AkkaHttpClientHttpTest extends HttpTest[Future] {
+
+ override implicit val backend: SttpBackend[Future, Nothing] = AkkaHttpBackend()
+ override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/streaming/AkkaHttpStreamingTests.scala b/akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpStreamingTests.scala
index 691df81..e8ab9d7 100644
--- a/tests/src/test/scala/com/softwaremill/sttp/streaming/AkkaHttpStreamingTests.scala
+++ b/akka-http-backend/src/test/scala/com/softwaremill/sttp/akkahttp/AkkaHttpStreamingTests.scala
@@ -1,17 +1,28 @@
-package com.softwaremill.sttp.streaming
+package com.softwaremill.sttp.akkahttp
import akka.NotUsed
import akka.actor.ActorSystem
-import akka.stream.Materializer
+import akka.stream.{ActorMaterializer, Materializer}
import akka.stream.scaladsl.Source
import akka.util.ByteString
import com.softwaremill.sttp.SttpBackend
-import com.softwaremill.sttp.akkahttp.AkkaHttpBackend
-import com.softwaremill.sttp.testing.streaming.{ConvertToFuture, TestStreamingBackend}
+import com.softwaremill.sttp.testing.ConvertToFuture
+import com.softwaremill.sttp.testing.streaming.{StreamingTest, TestStreamingBackend}
import scala.concurrent.Future
-class AkkaHttpStreamingTests(actorSystem: ActorSystem)(implicit materializer: Materializer)
+class AkkaHttpStreamingTest extends StreamingTest[Future, Source[ByteString, Any]] {
+
+ private implicit val actorSystem: ActorSystem = ActorSystem("sttp-test")
+ private implicit val materializer: ActorMaterializer = ActorMaterializer()
+
+ override val testStreamingBackend: TestStreamingBackend[Future, Source[ByteString, Any]] =
+ new AkkaHttpTestStreamingBackend(actorSystem)
+}
+
+class AkkaHttpTestStreamingBackend(
+ actorSystem: ActorSystem
+)(implicit materializer: Materializer)
extends TestStreamingBackend[Future, Source[ByteString, Any]] {
override implicit val backend: SttpBackend[Future, Source[ByteString, Any]] =
diff --git a/async-http-client-backend/cats/src/test/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHttpTest.scala b/async-http-client-backend/cats/src/test/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHttpTest.scala
new file mode 100644
index 0000000..5136914
--- /dev/null
+++ b/async-http-client-backend/cats/src/test/scala/com/softwaremill/sttp/asynchttpclient/cats/AsyncHttpClientCatsHttpTest.scala
@@ -0,0 +1,12 @@
+package com.softwaremill.sttp.asynchttpclient.cats
+
+import cats.effect.IO
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.cats.convertCatsIOToFuture
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+class AsyncHttpClientCatsHttpTest extends HttpTest[IO] {
+
+ override implicit val backend: SttpBackend[IO, Nothing] = AsyncHttpClientCatsBackend()
+ override implicit val convertToFuture: ConvertToFuture[IO] = convertCatsIOToFuture
+}
diff --git a/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpStreamingTest.scala b/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpStreamingTest.scala
new file mode 100644
index 0000000..565db5c
--- /dev/null
+++ b/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpStreamingTest.scala
@@ -0,0 +1,40 @@
+package com.softwaremill.sttp.asynchttpclient.fs2
+
+import java.nio.ByteBuffer
+
+import cats.effect.IO
+import cats.instances.string._
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.streaming.{StreamingTest, TestStreamingBackend}
+import com.softwaremill.sttp.testing.ConvertToFuture
+import fs2.{Chunk, Stream, text}
+import scala.concurrent.Future
+
+class AsyncHttpClientFs2HttpStreamingTest extends StreamingTest[IO, Stream[IO, ByteBuffer]] {
+
+ override val testStreamingBackend: TestStreamingBackend[IO, Stream[IO, ByteBuffer]] =
+ new AsyncHttpClientFs2TestStreamingBackend
+}
+
+class AsyncHttpClientFs2TestStreamingBackend extends TestStreamingBackend[IO, Stream[IO, ByteBuffer]] {
+
+ override implicit val backend: SttpBackend[IO, Stream[IO, ByteBuffer]] =
+ AsyncHttpClientFs2Backend[IO]()
+
+ override implicit val convertToFuture: ConvertToFuture[IO] =
+ new ConvertToFuture[IO] {
+ override def toFuture[T](value: IO[T]): Future[T] =
+ value.unsafeToFuture()
+ }
+
+ override def bodyProducer(body: String): Stream[IO, ByteBuffer] =
+ Stream.emits(body.getBytes("utf-8")).map(b => ByteBuffer.wrap(Array(b)))
+
+ override def bodyConsumer(stream: Stream[IO, ByteBuffer]): IO[String] =
+ stream
+ .map(bb => Chunk.array(bb.array))
+ .through(text.utf8DecodeC)
+ .compile
+ .foldMonoid
+
+}
diff --git a/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpTest.scala b/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpTest.scala
new file mode 100644
index 0000000..7772c23
--- /dev/null
+++ b/async-http-client-backend/fs2/src/test/scala/com/softwaremill/sttp/asynchttpclient/fs2/AsyncHttpClientFs2HttpTest.scala
@@ -0,0 +1,15 @@
+package com.softwaremill.sttp.asynchttpclient.fs2
+
+import cats.effect.IO
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scala.concurrent.Future
+
+class AsyncHttpClientFs2HttpTest extends HttpTest[IO] {
+
+ override implicit val backend: SttpBackend[IO, Nothing] = AsyncHttpClientFs2Backend()
+ override implicit val convertToFuture: ConvertToFuture[IO] = new ConvertToFuture[IO] {
+ override def toFuture[T](value: IO[T]): Future[T] = value.unsafeToFuture()
+ }
+}
diff --git a/async-http-client-backend/future/src/test/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHttpTest.scala b/async-http-client-backend/future/src/test/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHttpTest.scala
new file mode 100644
index 0000000..58d8aa8
--- /dev/null
+++ b/async-http-client-backend/future/src/test/scala/com/softwaremill/sttp/asynchttpclient/future/AsyncHttpClientFutureHttpTest.scala
@@ -0,0 +1,12 @@
+package com.softwaremill.sttp.asynchttpclient.future
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scala.concurrent.Future
+
+class AsyncHttpClientFutureHttpTest extends HttpTest[Future] {
+
+ override implicit val backend: SttpBackend[Future, Nothing] = AsyncHttpClientFutureBackend()
+ override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future
+}
diff --git a/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHttpTest.scala b/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHttpTest.scala
new file mode 100644
index 0000000..a08e738
--- /dev/null
+++ b/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixHttpTest.scala
@@ -0,0 +1,15 @@
+package com.softwaremill.sttp.asynchttpclient.monix
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.monix.convertMonixTaskToFuture
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+import monix.eval.Task
+
+class AsyncHttpClientMonixHttpTest extends HttpTest[Task] {
+
+ import monix.execution.Scheduler.Implicits.global
+
+ override implicit val backend: SttpBackend[Task, Nothing] = AsyncHttpClientMonixBackend()
+ override implicit val convertToFuture: ConvertToFuture[Task] = convertMonixTaskToFuture
+
+}
diff --git a/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixStreamingTest.scala b/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixStreamingTest.scala
new file mode 100644
index 0000000..34c15ff
--- /dev/null
+++ b/async-http-client-backend/monix/src/test/scala/com/softwaremill/sttp/asynchttpclient/monix/AsyncHttpClientMonixStreamingTest.scala
@@ -0,0 +1,26 @@
+package com.softwaremill.sttp.asynchttpclient.monix
+
+import java.nio.ByteBuffer
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.monix.MonixTestStreamingBackend
+import com.softwaremill.sttp.testing.streaming.{StreamingTest, TestStreamingBackend}
+import monix.eval.Task
+import monix.reactive.Observable
+
+class AsyncHttpClientMonixStreamingTest extends StreamingTest[Task, Observable[ByteBuffer]] {
+
+ override val testStreamingBackend: TestStreamingBackend[Task, Observable[ByteBuffer]] =
+ new AsyncHttpClientMonixTestStreamingBackend
+}
+
+class AsyncHttpClientMonixTestStreamingBackend extends MonixTestStreamingBackend[ByteBuffer] {
+
+ import monix.execution.Scheduler.Implicits.global
+
+ override def toByteArray(v: ByteBuffer): Array[Byte] = v.array()
+ override def fromByteArray(v: Array[Byte]): ByteBuffer = ByteBuffer.wrap(v)
+
+ override implicit val backend: SttpBackend[Task, Observable[ByteBuffer]] =
+ AsyncHttpClientMonixBackend()
+}
diff --git a/async-http-client-backend/scalaz/src/test/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHttpTest.scala b/async-http-client-backend/scalaz/src/test/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHttpTest.scala
new file mode 100644
index 0000000..67acc09
--- /dev/null
+++ b/async-http-client-backend/scalaz/src/test/scala/com/softwaremill/sttp/asynchttpclient/scalaz/AsyncHttpClientScalazHttpTest.scala
@@ -0,0 +1,13 @@
+package com.softwaremill.sttp.asynchttpclient.scalaz
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.scalaz.convertScalazTaskToFuture
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scalaz.concurrent.Task
+
+class AsyncHttpClientScalazHttpTest extends HttpTest[Task] {
+
+ override implicit val backend: SttpBackend[Task, Nothing] = AsyncHttpClientScalazBackend()
+ override implicit val convertToFuture: ConvertToFuture[Task] = convertScalazTaskToFuture
+}
diff --git a/build.sbt b/build.sbt
index 1f510e1..461a88e 100644
--- a/build.sbt
+++ b/build.sbt
@@ -34,9 +34,12 @@ val akkaStreams = "com.typesafe.akka" %% "akka-stream" % "2.5.12"
val scalaTest = "org.scalatest" %% "scalatest" % "3.0.5"
+val testServerPort = settingKey[Int]("Port to run the http test server on")
+val startTestServer = taskKey[Unit]("Start a http server used by tests")
+
lazy val rootProject = (project in file("."))
.settings(commonSettings: _*)
- .settings(publishArtifact := false, name := "sttp")
+ .settings(skip in publish := true, name := "sttp")
.aggregate(
core,
cats,
@@ -55,7 +58,8 @@ lazy val rootProject = (project in file("."))
json4s,
braveBackend,
prometheusBackend,
- tests
+ tests,
+ testServer
)
lazy val core: Project = (project in file("core"))
@@ -63,9 +67,10 @@ lazy val core: Project = (project in file("core"))
.settings(
name := "core",
libraryDependencies ++= Seq(
- "org.scalacheck" %% "scalacheck" % "1.14.0" % "test",
+ "com.github.pathikrit" %% "better-files" % "3.4.0" % "test",
scalaTest % "test"
- )
+ ),
+ publishArtifact in Test := true // allow implementations outside of this repo
)
//----- implementations
@@ -97,6 +102,7 @@ lazy val scalaz: Project = (project in file("implementations/scalaz"))
//-- akka
lazy val akkaHttpBackend: Project = (project in file("akka-http-backend"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(
name := "akka-http-backend",
libraryDependencies ++= Seq(
@@ -106,42 +112,45 @@ lazy val akkaHttpBackend: Project = (project in file("akka-http-backend"))
akkaStreams % "provided"
)
)
- .dependsOn(core)
+ .dependsOn(core % "compile->compile;test->test")
//-- async http client
lazy val asyncHttpClientBackend: Project = {
(project in file("async-http-client-backend"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(
name := "async-http-client-backend",
libraryDependencies ++= Seq(
"org.asynchttpclient" % "async-http-client" % "2.4.7"
)
)
- .dependsOn(core)
+ .dependsOn(core % "compile->compile;test->test")
}
def asyncHttpClientBackendProject(proj: String): Project = {
Project(s"asyncHttpClientBackend${proj.capitalize}", file(s"async-http-client-backend/$proj"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(name := s"async-http-client-backend-$proj")
.dependsOn(asyncHttpClientBackend)
}
lazy val asyncHttpClientFutureBackend: Project =
asyncHttpClientBackendProject("future")
+ .dependsOn(core % "compile->compile;test->test")
lazy val asyncHttpClientScalazBackend: Project =
asyncHttpClientBackendProject("scalaz")
- .dependsOn(scalaz)
+ .dependsOn(scalaz % "compile->compile;test->test")
lazy val asyncHttpClientMonixBackend: Project =
asyncHttpClientBackendProject("monix")
- .dependsOn(monix)
+ .dependsOn(monix % "compile->compile;test->test")
lazy val asyncHttpClientCatsBackend: Project =
asyncHttpClientBackendProject("cats")
- .dependsOn(cats)
+ .dependsOn(cats % "compile->compile;test->test")
lazy val asyncHttpClientFs2Backend: Project =
asyncHttpClientBackendProject("fs2")
@@ -150,28 +159,31 @@ lazy val asyncHttpClientFs2Backend: Project =
"com.github.zainab-ali" %% "fs2-reactive-streams" % "0.5.1"
)
)
+ .dependsOn(core % "compile->compile;test->test")
//-- okhttp
lazy val okhttpBackend: Project = (project in file("okhttp-backend"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(
name := "okhttp-backend",
libraryDependencies ++= Seq(
"com.squareup.okhttp3" % "okhttp" % "3.10.0"
)
)
- .dependsOn(core)
+ .dependsOn(core % "compile->compile;test->test")
def okhttpBackendProject(proj: String): Project = {
Project(s"okhttpBackend${proj.capitalize}", file(s"okhttp-backend/$proj"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(name := s"okhttp-backend-$proj")
.dependsOn(okhttpBackend)
}
lazy val okhttpMonixBackend: Project =
okhttpBackendProject("monix")
- .dependsOn(monix)
+ .dependsOn(monix % "compile->compile;test->test")
lazy val circeVersion = "0.9.3"
@@ -225,8 +237,9 @@ lazy val prometheusBackend: Project = (project in file("metrics/prometheus-backe
lazy val tests: Project = (project in file("tests"))
.settings(commonSettings: _*)
+ .settings(testServerSettings: _*)
.settings(
- publishArtifact := false,
+ skip in publish := true,
name := "tests",
libraryDependencies ++= Seq(
akkaHttp,
@@ -251,3 +264,26 @@ lazy val tests: Project = (project in file("tests"))
asyncHttpClientFs2Backend,
okhttpMonixBackend
)
+
+// https://stackoverflow.com/questions/25766797/how-do-i-start-a-server-before-running-a-test-suite-in-sbt
+lazy val testServer: Project = project
+ .in(file("test-server"))
+ .settings(commonSettings: _*)
+ .settings(
+ name := "test-server",
+ libraryDependencies ++= Seq(akkaHttp, akkaStreams),
+ mainClass in reStart := Some("com.softwaremill.sttp.server.TestHttpServer"),
+ reStartArgs := Seq(s"${testServerPort.value}"),
+ testServerPort := 51823,
+ startTestServer := (reStart in Test).toTask("").value
+ )
+
+// maybe use IntegrationTest instead of Test?
+lazy val testServerSettings = Seq(
+ test in Test := (test in Test).dependsOn(startTestServer in testServer).value,
+ testOnly in Test := (testOnly in Test).dependsOn(startTestServer in testServer).evaluated,
+ testOptions in Test += Tests.Setup(() => {
+ val port = (testServerPort in testServer).value
+ PollingUtils.waitUntilServerAvailable(new URL(s"http://localhost:$port"))
+ })
+)
diff --git a/core/src/main/scala/com/softwaremill/sttp/MonadError.scala b/core/src/main/scala/com/softwaremill/sttp/MonadError.scala
index a783765..b5b1852 100644
--- a/core/src/main/scala/com/softwaremill/sttp/MonadError.scala
+++ b/core/src/main/scala/com/softwaremill/sttp/MonadError.scala
@@ -31,6 +31,15 @@ trait MonadAsyncError[R[_]] extends MonadError[R] {
def async[T](register: (Either[Throwable, T] => Unit) => Unit): R[T]
}
+object syntax {
+
+ implicit final class MonadErrorOps[R[_], A](val r: R[A]) extends AnyVal {
+ def map[B](f: A => B)(implicit ME: MonadError[R]): R[B] = ME.map(r)(f)
+ def flatMap[B](f: A => R[B])(implicit ME: MonadError[R]): R[B] =
+ ME.flatMap(r)(f)
+ }
+}
+
object IdMonad extends MonadError[Id] {
override def unit[T](t: T): Id[T] = t
override def map[T, T2](fa: Id[T])(f: (T) => T2): Id[T2] = f(fa)
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/streaming/ConvertToFuture.scala b/core/src/test/scala/com/softwaremill/sttp/testing/ConvertToFuture.scala
index 9438890..25a7d8e 100644
--- a/core/src/test/scala/com/softwaremill/sttp/testing/streaming/ConvertToFuture.scala
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/ConvertToFuture.scala
@@ -1,4 +1,4 @@
-package com.softwaremill.sttp.testing.streaming
+package com.softwaremill.sttp.testing
import com.softwaremill.sttp.Id
import scala.concurrent.Future
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/CustomMatchers.scala b/core/src/test/scala/com/softwaremill/sttp/testing/CustomMatchers.scala
new file mode 100644
index 0000000..a6984c8
--- /dev/null
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/CustomMatchers.scala
@@ -0,0 +1,22 @@
+package com.softwaremill.sttp.testing
+
+import java.nio.file.{Files, Paths}
+import java.{io, util}
+
+import org.scalatest.matchers.{MatchResult, Matcher}
+
+object CustomMatchers {
+ class FileContentsMatch(file: java.io.File) extends Matcher[java.io.File] {
+ override def apply(left: io.File): MatchResult = {
+ val inBA = Files.readAllBytes(Paths.get(left.getAbsolutePath))
+ val expectedBA = Files.readAllBytes(Paths.get(file.getAbsolutePath))
+ MatchResult(
+ util.Arrays.equals(inBA, expectedBA),
+ "The files' contents are not the same",
+ "The files' contents are the same"
+ )
+ }
+ }
+
+ def haveSameContentAs(file: io.File) = new FileContentsMatch(file)
+}
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/ForceWrapped.scala b/core/src/test/scala/com/softwaremill/sttp/testing/ForceWrapped.scala
new file mode 100644
index 0000000..f73833f
--- /dev/null
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/ForceWrapped.scala
@@ -0,0 +1,30 @@
+package com.softwaremill.sttp.testing
+
+import org.scalatest.Suite
+import org.scalatest.concurrent.{PatienceConfiguration, ScalaFutures}
+import org.scalatest.exceptions.TestFailedException
+
+import scala.concurrent.Future
+import scala.concurrent.duration._
+import scala.language.higherKinds
+
+trait ForceWrapped extends ScalaFutures with TestingPatience { this: Suite =>
+
+ implicit class ForceDecorator[R[_], T](wrapped: R[T]) {
+ def toFuture()(implicit ctf: ConvertToFuture[R]): Future[T] =
+ ctf.toFuture(wrapped)
+
+ def force()(implicit ctf: ConvertToFuture[R]): T = {
+ try {
+ ctf.toFuture(wrapped).futureValue
+ } catch {
+ case e: TestFailedException if e.getCause != null => throw e.getCause
+ }
+ }
+ }
+}
+
+trait TestingPatience extends PatienceConfiguration {
+ override implicit val patienceConfig: PatienceConfig =
+ PatienceConfig(timeout = 5.seconds, interval = 150.milliseconds)
+}
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala b/core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala
new file mode 100644
index 0000000..f1fa002
--- /dev/null
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala
@@ -0,0 +1,526 @@
+package com.softwaremill.sttp.testing
+
+import java.io.{ByteArrayInputStream, IOException}
+import java.nio.ByteBuffer
+import java.nio.file.Paths
+import java.time.{ZoneId, ZonedDateTime}
+
+import better.files._
+import com.softwaremill.sttp._
+import com.softwaremill.sttp.testing.CustomMatchers._
+import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
+import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach, FreeSpec, Matchers, OptionValues}
+
+import scala.concurrent.duration._
+import scala.language.higherKinds
+
+trait HttpTest[R[_]]
+ extends FreeSpec
+ with Matchers
+ with ForceWrapped
+ with ScalaFutures
+ with OptionValues
+ with IntegrationPatience
+ with BeforeAndAfterEach
+ with BeforeAndAfterAll {
+
+ private val endpoint = "localhost:51823"
+
+ override def afterEach() {
+ val file = File(outPath)
+ if (file.exists) file.delete()
+ }
+
+ private val textFile =
+ new java.io.File("test-server/src/main/resources/textfile.txt")
+ private val binaryFile =
+ new java.io.File("test-server/src/main/resources/binaryfile.jpg")
+ private val outPath = File.newTemporaryDirectory().path
+ private val textWithSpecialCharacters = "Żółć!"
+
+ implicit val backend: SttpBackend[R, Nothing]
+ implicit val convertToFuture: ConvertToFuture[R]
+
+ private val postEcho = sttp.post(uri"$endpoint/echo")
+ private val testBody = "this is the body"
+ private val testBodyBytes = testBody.getBytes("UTF-8")
+ private val expectedPostEchoResponse = "POST /echo this is the body"
+
+ private val sttpIgnore = com.softwaremill.sttp.ignore
+
+ "parse response" - {
+ "as string" in {
+ val response = postEcho.body(testBody).send().force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ }
+
+ "as string with mapping using map" in {
+ val response = postEcho
+ .body(testBody)
+ .response(asString.map(_.length))
+ .send()
+ .force()
+ response.unsafeBody should be(expectedPostEchoResponse.length)
+ }
+
+ "as string with mapping using mapResponse" in {
+ val response = postEcho
+ .body(testBody)
+ .mapResponse(_.length)
+ .send()
+ .force()
+ response.unsafeBody should be(expectedPostEchoResponse.length)
+ }
+
+ "as a byte array" in {
+ val response =
+ postEcho.body(testBody).response(asByteArray).send().force()
+ val fc = new String(response.unsafeBody, "UTF-8")
+ fc should be(expectedPostEchoResponse)
+ }
+
+ "as parameters" in {
+ val params = List("a" -> "b", "c" -> "d", "e=" -> "&f")
+ val response = sttp
+ .post(uri"$endpoint/echo/form_params/as_params")
+ .body(params: _*)
+ .response(asParams)
+ .send()
+ .force()
+ response.unsafeBody.toList should be(params)
+ }
+ }
+
+ "parameters" - {
+ "make a get request with parameters" in {
+ val response = sttp
+ .get(uri"$endpoint/echo?p2=v2&p1=v1")
+ .send()
+ .force()
+
+ response.unsafeBody should be("GET /echo p1=v1 p2=v2")
+ }
+ }
+
+ "body" - {
+ "post a string" in {
+ val response = postEcho.body(testBody).send().force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ }
+
+ "post a byte array" in {
+ val response =
+ postEcho.body(testBodyBytes).send().force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ }
+
+ "post an input stream" in {
+ val response = postEcho
+ .body(new ByteArrayInputStream(testBodyBytes))
+ .send()
+ .force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ }
+
+ "post a byte buffer" in {
+ val response = postEcho
+ .body(ByteBuffer.wrap(testBodyBytes))
+ .send()
+ .force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ }
+
+ "post a file" in {
+ val f = File.newTemporaryFile().write(testBody)
+ try {
+ val response = postEcho.body(f.toJava).send().force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ } finally f.delete()
+ }
+
+ "post a path" in {
+ val f = File.newTemporaryFile().write(testBody)
+ try {
+ val response =
+ postEcho.body(f.toJava.toPath).send().force()
+ response.unsafeBody should be(expectedPostEchoResponse)
+ } finally f.delete()
+ }
+
+ "post form data" in {
+ val response = sttp
+ .post(uri"$endpoint/echo/form_params/as_string")
+ .body("a" -> "b", "c" -> "d")
+ .send()
+ .force()
+ response.unsafeBody should be("a=b c=d")
+ }
+
+ "post form data with special characters" in {
+ val response = sttp
+ .post(uri"$endpoint/echo/form_params/as_string")
+ .body("a=" -> "/b", "c:" -> "/d")
+ .send()
+ .force()
+ response.unsafeBody should be("a==/b c:=/d")
+ }
+
+ "post without a body" in {
+ val response = postEcho.send().force()
+ response.unsafeBody should be("POST /echo")
+ }
+ }
+
+ "headers" - {
+ val getHeaders = sttp.get(uri"$endpoint/set_headers")
+
+ "read response headers" in {
+ val response = getHeaders.response(sttpIgnore).send().force()
+ response.headers should have length (6)
+ response.headers("Cache-Control").toSet should be(Set("no-cache", "max-age=1000"))
+ response.header("Server") should be('defined)
+ response.header("server") should be('defined)
+ response.header("Server").get should startWith("akka-http")
+ response.contentType should be(Some("text/plain; charset=UTF-8"))
+ response.contentLength should be(Some(2L))
+ }
+ }
+
+ "errors" - {
+ val getHeaders = sttp.post(uri"$endpoint/set_headers")
+
+ "return 405 when method not allowed" in {
+ val response = getHeaders.response(sttpIgnore).send().force()
+ response.code should be(405)
+ response.isClientError should be(true)
+ response.body should be('left)
+ }
+ }
+
+ "cookies" - {
+ "read response cookies" in {
+ val response =
+ sttp
+ .get(uri"$endpoint/set_cookies")
+ .response(sttpIgnore)
+ .send()
+ .force()
+ response.cookies should have length (3)
+ response.cookies.toSet should be(
+ Set(
+ Cookie("cookie1", "value1", secure = true, httpOnly = true, maxAge = Some(123L)),
+ Cookie("cookie2", "value2"),
+ Cookie("cookie3", "", domain = Some("xyz"), path = Some("a/b/c"))
+ ))
+ }
+
+ "read response cookies with the expires attribute" in {
+ val response = sttp
+ .get(uri"$endpoint/set_cookies/with_expires")
+ .response(sttpIgnore)
+ .send()
+ .force()
+ response.cookies should have length (1)
+ val c = response.cookies(0)
+
+ c.name should be("c")
+ c.value should be("v")
+ c.expires.map(_.toInstant.toEpochMilli) should be(
+ Some(
+ ZonedDateTime
+ .of(1997, 12, 8, 12, 49, 12, 0, ZoneId.of("GMT"))
+ .toInstant
+ .toEpochMilli
+ ))
+ }
+ }
+
+ "auth" - {
+ val secureBasic = sttp.get(uri"$endpoint/secure_basic")
+
+ "return a 401 when authorization fails" in {
+ val req = secureBasic
+ val resp = req.send().force()
+ resp.code should be(401)
+ resp.header("WWW-Authenticate") should be(Some("""Basic realm="test realm",charset=UTF-8"""))
+ }
+
+ "perform basic authorization" in {
+ val req = secureBasic.auth.basic("adam", "1234")
+ val resp = req.send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be("Hello, adam!")
+ }
+ }
+
+ "compression" - {
+ val compress = sttp.get(uri"$endpoint/compress")
+ val decompressedBody = "I'm compressed!"
+
+ "decompress using the default accept encoding header" in {
+ val req = compress
+ val resp = req.send().force()
+ resp.unsafeBody should be(decompressedBody)
+ }
+
+ "decompress using gzip" in {
+ val req =
+ compress.header("Accept-Encoding", "gzip", replaceExisting = true)
+ val resp = req.send().force()
+ resp.unsafeBody should be(decompressedBody)
+ }
+
+ "decompress using deflate" in {
+ val req =
+ compress.header("Accept-Encoding", "deflate", replaceExisting = true)
+ val resp = req.send().force()
+ resp.unsafeBody should be(decompressedBody)
+ }
+
+ "work despite providing an unsupported encoding" in {
+ val req =
+ compress.header("Accept-Encoding", "br", replaceExisting = true)
+ val resp = req.send().force()
+ resp.unsafeBody should be(decompressedBody)
+ }
+ }
+
+ "download file" - {
+
+ "download a binary file using asFile" in {
+ val file = outPath.resolve("binaryfile.jpg").toFile
+ val req =
+ sttp.get(uri"$endpoint/download/binary").response(asFile(file))
+ val resp = req.send().force()
+
+ resp.unsafeBody shouldBe file
+ file should exist
+ file should haveSameContentAs(binaryFile)
+ }
+
+ "download a text file using asFile" in {
+ val file = outPath.resolve("textfile.txt").toFile
+ val req =
+ sttp.get(uri"$endpoint/download/text").response(asFile(file))
+ val resp = req.send().force()
+
+ resp.unsafeBody shouldBe file
+ file should exist
+ file should haveSameContentAs(textFile)
+ }
+
+ "download a binary file using asPath" in {
+ val path = outPath.resolve("binaryfile.jpg")
+ val req =
+ sttp.get(uri"$endpoint/download/binary").response(asPath(path))
+ val resp = req.send().force()
+
+ resp.unsafeBody shouldBe path
+ path.toFile should exist
+ path.toFile should haveSameContentAs(binaryFile)
+ }
+
+ "download a text file using asPath" in {
+ val path = outPath.resolve("textfile.txt")
+ val req =
+ sttp.get(uri"$endpoint/download/text").response(asPath(path))
+ val resp = req.send().force()
+
+ resp.unsafeBody shouldBe path
+ path.toFile should exist
+ path.toFile should haveSameContentAs(textFile)
+ }
+
+ "fail at trying to save file to a restricted location" in {
+ val path = Paths.get("/").resolve("textfile.txt")
+ val req =
+ sttp.get(uri"$endpoint/download/text").response(asPath(path))
+ val caught = intercept[IOException] {
+ req.send().force()
+ }
+
+ caught.getMessage shouldBe "Permission denied"
+ }
+
+ "fail when file exists and overwrite flag is false" in {
+ val path = outPath.resolve("textfile.txt")
+ path.toFile.getParentFile.mkdirs()
+ path.toFile.createNewFile()
+ val req =
+ sttp.get(uri"$endpoint/download/text").response(asPath(path))
+
+ val caught = intercept[IOException] {
+ req.send().force()
+ }
+
+ caught.getMessage shouldBe s"File ${path.toFile.getAbsolutePath} exists - overwriting prohibited"
+
+ }
+
+ "not fail when file exists and overwrite flag is true" in {
+ val path = outPath.resolve("textfile.txt")
+ path.toFile.getParentFile.mkdirs()
+ path.toFile.createNewFile()
+ val req =
+ sttp
+ .get(uri"$endpoint/download/text")
+ .response(asPath(path, overwrite = true))
+ val resp = req.send().force()
+
+ resp.unsafeBody shouldBe path
+ path.toFile should exist
+ path.toFile should haveSameContentAs(textFile)
+ }
+ }
+
+ "multipart" - {
+ val mp = sttp.post(uri"$endpoint/multipart")
+
+ "send a multipart message" in {
+ val req = mp.multipartBody(multipart("p1", "v1"), multipart("p2", "v2"))
+ val resp = req.send().force()
+ resp.unsafeBody should be("p1=v1, p2=v2")
+ }
+
+ "send a multipart message with filenames" in {
+ val req = mp.multipartBody(multipart("p1", "v1").fileName("f1"), multipart("p2", "v2").fileName("f2"))
+ val resp = req.send().force()
+ resp.unsafeBody should be("p1=v1 (f1), p2=v2 (f2)")
+ }
+
+ "send a multipart message with a file" in {
+ val f = File.newTemporaryFile().write(testBody)
+ try {
+ val req =
+ mp.multipartBody(multipart("p1", f.toJava), multipart("p2", "v2"))
+ val resp = req.send().force()
+ resp.unsafeBody should be(s"p1=$testBody (${f.name}), p2=v2")
+ } finally f.delete()
+ }
+ }
+
+ "redirect" - {
+ val r1 = sttp.post(uri"$endpoint/redirect/r1")
+ val r2 = sttp.post(uri"$endpoint/redirect/r2")
+ val r3 = sttp.post(uri"$endpoint/redirect/r3")
+ val r4response = "819"
+ val loop = sttp.post(uri"$endpoint/redirect/loop")
+
+ "not redirect when redirects shouldn't be followed (temporary)" in {
+ val resp = r1.followRedirects(false).send().force()
+ resp.code should be(307)
+ resp.body should be('left)
+ resp.history should be('empty)
+ }
+
+ "not redirect when redirects shouldn't be followed (permanent)" in {
+ val resp = r2.followRedirects(false).send().force()
+ resp.code should be(308)
+ resp.body should be('left)
+ }
+
+ "redirect when redirects should be followed" in {
+ val resp = r2.send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be(r4response)
+ }
+
+ "redirect twice when redirects should be followed" in {
+ val resp = r1.send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be(r4response)
+ }
+
+ "redirect when redirects should be followed, and the response is parsed" in {
+ val resp = r2.response(asString.map(_.toInt)).send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be(r4response.toInt)
+ }
+
+ "keep a single history entry of redirect responses" in {
+ val resp = r3.send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be(r4response)
+ resp.history should have size (1)
+ resp.history(0).code should be(302)
+ }
+
+ "keep whole history of redirect responses" in {
+ val resp = r1.send().force()
+ resp.code should be(200)
+ resp.unsafeBody should be(r4response)
+ resp.history should have size (3)
+ resp.history(0).code should be(307)
+ resp.history(1).code should be(308)
+ resp.history(2).code should be(302)
+ }
+
+ "break redirect loops" in {
+ val resp = loop.send().force()
+ resp.code should be(0)
+ resp.history should have size (FollowRedirectsBackend.MaxRedirects)
+ }
+
+ "break redirect loops after user-specified count" in {
+ val maxRedirects = 10
+ val resp = loop.maxRedirects(maxRedirects).send().force()
+ resp.code should be(0)
+ resp.history should have size (maxRedirects)
+ }
+
+ "not redirect when maxRedirects is less than or equal to 0" in {
+ val resp = loop.maxRedirects(-1).send().force()
+ resp.code should be(302)
+ resp.body should be('left)
+ resp.history should be('empty)
+ }
+ }
+
+ "timeout" - {
+ "fail if read timeout is not big enough" in {
+ val request = sttp
+ .get(uri"$endpoint/timeout")
+ .readTimeout(200.milliseconds)
+ .response(asString)
+
+ intercept[Throwable] {
+ request.send().force()
+ }
+ }
+
+ "not fail if read timeout is big enough" in {
+ val request = sttp
+ .get(uri"$endpoint/timeout")
+ .readTimeout(5.seconds)
+ .response(asString)
+
+ request.send().force().unsafeBody should be("Done")
+ }
+ }
+
+ "empty response" - {
+ val postEmptyResponse = sttp
+ .post(uri"$endpoint/empty_unauthorized_response")
+ .body("{}")
+ .contentType("application/json")
+
+ "parse an empty error response as empty string" in {
+ val response = postEmptyResponse.send().force()
+ response.body should be(Left(""))
+ }
+ }
+
+ "encoding" - {
+ "read response body encoded using ISO-8859-2, as specified in the header, overriding the default" in {
+ val request = sttp.get(uri"$endpoint/respond_with_iso_8859_2")
+
+ request.send().force().unsafeBody should be(textWithSpecialCharacters)
+ }
+ }
+
+ override protected def afterAll(): Unit = {
+ backend.close()
+ super.afterAll()
+ }
+
+}
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/streaming/StreamingTest.scala b/core/src/test/scala/com/softwaremill/sttp/testing/streaming/StreamingTest.scala
new file mode 100644
index 0000000..aa0af07
--- /dev/null
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/streaming/StreamingTest.scala
@@ -0,0 +1,65 @@
+package com.softwaremill.sttp.testing.streaming
+
+import com.softwaremill.sttp._
+import com.softwaremill.sttp.testing.ForceWrapped
+import org.scalatest.{AsyncFreeSpec, BeforeAndAfterAll, Matchers}
+
+import scala.language.higherKinds
+
+trait StreamingTest[R[_], S] extends AsyncFreeSpec with Matchers with BeforeAndAfterAll with ForceWrapped {
+
+ private val endpoint = "localhost:51823"
+ private val body = "streaming test"
+
+ val testStreamingBackend: TestStreamingBackend[R, S]
+ import testStreamingBackend._
+
+ "stream request body" in {
+ sttp
+ .post(uri"$endpoint/streaming/echo")
+ .streamBody(bodyProducer(body))
+ .send()
+ .toFuture()
+ .map { response =>
+ response.unsafeBody shouldBe body
+ }
+ }
+
+ "receive a stream" in {
+ sttp
+ .post(uri"$endpoint/streaming/echo")
+ .body(body)
+ .response(asStream[S])
+ .send()
+ .toFuture()
+ .flatMap { response =>
+ bodyConsumer(response.unsafeBody).toFuture()
+ }
+ .map { responseBody =>
+ responseBody shouldBe body
+ }
+ }
+
+ "receive a stream from an https site" in {
+ sttp
+ // of course, you should never rely on the internet being available
+ // in tests, but that's so much easier than setting up an https
+ // testing server
+ .get(uri"https://softwaremill.com")
+ .response(asStream[S])
+ .send()
+ .toFuture()
+ .flatMap { response =>
+ bodyConsumer(response.unsafeBody).toFuture()
+ }
+ .map { responseBody =>
+ responseBody should include("</div>")
+ }
+ }
+
+ override protected def afterAll(): Unit = {
+ testStreamingBackend.backend.close()
+ super.afterAll()
+ }
+
+}
diff --git a/core/src/test/scala/com/softwaremill/sttp/testing/streaming/TestStreamingBackend.scala b/core/src/test/scala/com/softwaremill/sttp/testing/streaming/TestStreamingBackend.scala
index 266c402..3ea63a3 100644
--- a/core/src/test/scala/com/softwaremill/sttp/testing/streaming/TestStreamingBackend.scala
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/streaming/TestStreamingBackend.scala
@@ -1,6 +1,7 @@
package com.softwaremill.sttp.testing.streaming
import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.ConvertToFuture
import scala.language.higherKinds
diff --git a/implementations/cats/src/test/scala/com/softwaremill/sttp/impl/cats/package.scala b/implementations/cats/src/test/scala/com/softwaremill/sttp/impl/cats/package.scala
index abffc90..5f3db65 100644
--- a/implementations/cats/src/test/scala/com/softwaremill/sttp/impl/cats/package.scala
+++ b/implementations/cats/src/test/scala/com/softwaremill/sttp/impl/cats/package.scala
@@ -1,7 +1,7 @@
package com.softwaremill.sttp.impl
import _root_.cats.effect.IO
-import com.softwaremill.sttp.testing.streaming.ConvertToFuture
+import com.softwaremill.sttp.testing.ConvertToFuture
import scala.concurrent.Future
diff --git a/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/MonixTestStreamingBackend.scala b/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/MonixTestStreamingBackend.scala
index 3f84ec3..f1b262b 100644
--- a/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/MonixTestStreamingBackend.scala
+++ b/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/MonixTestStreamingBackend.scala
@@ -1,21 +1,27 @@
package com.softwaremill.sttp.impl.monix
-import java.nio.ByteBuffer
-
-import com.softwaremill.sttp.testing.streaming.{ConvertToFuture, TestStreamingBackend}
+import com.softwaremill.sttp.testing.ConvertToFuture
+import com.softwaremill.sttp.testing.streaming.TestStreamingBackend
import monix.eval.Task
import monix.reactive.Observable
-trait MonixTestStreamingBackend extends TestStreamingBackend[Task, Observable[ByteBuffer]] {
+trait MonixTestStreamingBackend[T] extends TestStreamingBackend[Task, Observable[T]] {
+
+ def toByteArray(v: T): Array[Byte]
+ def fromByteArray(v: Array[Byte]): T
override implicit def convertToFuture: ConvertToFuture[Task] = convertMonixTaskToFuture
- override def bodyProducer(body: String): Observable[ByteBuffer] =
- Observable.fromIterable(body.getBytes("utf-8").map(b => ByteBuffer.wrap(Array(b))))
+ override def bodyProducer(body: String): Observable[T] =
+ Observable
+ .fromIterable(
+ body.getBytes("utf-8")
+ )
+ .map(v => fromByteArray(Array(v)))
- override def bodyConsumer(stream: Observable[ByteBuffer]): Task[String] =
+ override def bodyConsumer(stream: Observable[T]): Task[String] =
stream
- .flatMap(bb => Observable.fromIterable(bb.array()))
+ .flatMap(v => Observable.fromIterable(toByteArray(v)))
.toListL
.map(bs => new String(bs.toArray, "utf8"))
diff --git a/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/package.scala b/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/package.scala
index f77aa93..02fef8b 100644
--- a/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/package.scala
+++ b/implementations/monix/src/test/scala/com/softwaremill/sttp/impl/monix/package.scala
@@ -3,7 +3,7 @@ package com.softwaremill.sttp.impl
import scala.concurrent.Future
import _root_.monix.eval.Task
-import com.softwaremill.sttp.testing.streaming.ConvertToFuture
+import com.softwaremill.sttp.testing.ConvertToFuture
package object monix {
diff --git a/implementations/scalaz/src/test/scala/com/softwaremill/sttp/impl/scalaz/package.scala b/implementations/scalaz/src/test/scala/com/softwaremill/sttp/impl/scalaz/package.scala
index 8ac6446..27b4759 100644
--- a/implementations/scalaz/src/test/scala/com/softwaremill/sttp/impl/scalaz/package.scala
+++ b/implementations/scalaz/src/test/scala/com/softwaremill/sttp/impl/scalaz/package.scala
@@ -1,6 +1,6 @@
package com.softwaremill.sttp.impl
-import com.softwaremill.sttp.testing.streaming.ConvertToFuture
+import com.softwaremill.sttp.testing.ConvertToFuture
import _root_.scalaz.concurrent.Task
import _root_.scalaz.{-\/, \/-}
diff --git a/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHttpTest.scala b/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHttpTest.scala
new file mode 100644
index 0000000..4644bda
--- /dev/null
+++ b/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixHttpTest.scala
@@ -0,0 +1,12 @@
+package com.softwaremill.sttp.okhttp.monix
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.monix.convertMonixTaskToFuture
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+import monix.eval.Task
+
+class OkHttpMonixHttpTest extends HttpTest[Task] {
+
+ override implicit val backend: SttpBackend[Task, Nothing] = OkHttpMonixBackend()
+ override implicit val convertToFuture: ConvertToFuture[Task] = convertMonixTaskToFuture
+}
diff --git a/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixStreamingTest.scala b/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixStreamingTest.scala
new file mode 100644
index 0000000..af9ea9c
--- /dev/null
+++ b/okhttp-backend/monix/src/test/scala/com/softwaremill/sttp/okhttp/monix/OkHttpMonixStreamingTest.scala
@@ -0,0 +1,26 @@
+package com.softwaremill.sttp.okhttp.monix
+
+import java.nio.ByteBuffer
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.impl.monix.MonixTestStreamingBackend
+import com.softwaremill.sttp.testing.streaming.{StreamingTest, TestStreamingBackend}
+import monix.eval.Task
+import monix.reactive.Observable
+
+class OkHttpMonixStreamingTest extends StreamingTest[Task, Observable[ByteBuffer]] {
+
+ override val testStreamingBackend: TestStreamingBackend[Task, Observable[ByteBuffer]] =
+ new OkHttpMonixTestStreamingBackend
+}
+
+class OkHttpMonixTestStreamingBackend extends MonixTestStreamingBackend[ByteBuffer] {
+
+ import monix.execution.Scheduler.Implicits.global
+
+ override def toByteArray(v: ByteBuffer): Array[Byte] = v.array()
+ override def fromByteArray(v: Array[Byte]): ByteBuffer = ByteBuffer.wrap(v)
+
+ override implicit val backend: SttpBackend[Task, Observable[ByteBuffer]] =
+ OkHttpMonixBackend()
+}
diff --git a/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpFutureHttpTest.scala b/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpFutureHttpTest.scala
new file mode 100644
index 0000000..8ceda94
--- /dev/null
+++ b/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpFutureHttpTest.scala
@@ -0,0 +1,12 @@
+package com.softwaremill.sttp.okhttp
+
+import com.softwaremill.sttp.SttpBackend
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scala.concurrent.Future
+
+class OkHttpFutureHttpTest extends HttpTest[Future] {
+
+ override implicit val backend: SttpBackend[Future, Nothing] = OkHttpFutureBackend()
+ override implicit val convertToFuture: ConvertToFuture[Future] = ConvertToFuture.future
+}
diff --git a/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpSyncHttpTest.scala b/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpSyncHttpTest.scala
new file mode 100644
index 0000000..bae87d7
--- /dev/null
+++ b/okhttp-backend/src/test/scala/com/softwaremill/sttp/okhttp/OkHttpSyncHttpTest.scala
@@ -0,0 +1,10 @@
+package com.softwaremill.sttp.okhttp
+
+import com.softwaremill.sttp.{Id, SttpBackend}
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+class OkHttpSyncHttpTest extends HttpTest[Id] {
+
+ override implicit val backend: SttpBackend[Id, Nothing] = OkHttpSyncBackend()
+ override implicit val convertToFuture: ConvertToFuture[Id] = ConvertToFuture.id
+}
diff --git a/project/PollingUtils.scala b/project/PollingUtils.scala
new file mode 100644
index 0000000..34b92ac
--- /dev/null
+++ b/project/PollingUtils.scala
@@ -0,0 +1,45 @@
+import java.io.FileNotFoundException
+import java.net.{ConnectException, URL}
+
+import scala.concurrent.TimeoutException
+import scala.concurrent.duration._
+
+object PollingUtils {
+
+ def waitUntilServerAvailable(url: URL): Unit = {
+ val connected = poll(5.seconds, 250.milliseconds)({
+ urlConnectionAvailable(url)
+ })
+ if (!connected) {
+ throw new TimeoutException(s"Failed to connect to $url")
+ }
+ }
+
+ def poll(timeout: FiniteDuration, interval: FiniteDuration)(poll: => Boolean): Boolean = {
+ val start = System.nanoTime()
+
+ def go(): Boolean = {
+ if (poll) {
+ true
+ } else if ((System.nanoTime() - start) > timeout.toNanos) {
+ false
+ } else {
+ Thread.sleep(interval.toMillis)
+ go()
+ }
+ }
+ go()
+ }
+
+ def urlConnectionAvailable(url: URL): Boolean = {
+ try {
+ url.openConnection()
+ .getInputStream
+ .close()
+ true
+ } catch {
+ case _: ConnectException => false
+ case _: FileNotFoundException => true // on 404
+ }
+ }
+}
diff --git a/project/plugins.sbt b/project/plugins.sbt
index 1d83f24..b334460 100644
--- a/project/plugins.sbt
+++ b/project/plugins.sbt
@@ -1,4 +1,5 @@
-addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.15")
+// using '-coursier' because of https://github.com/lucidsoftware/neo-sbt-scalafmt/issues/64
+addSbtPlugin("com.lucidchart" % "sbt-scalafmt-coursier" % "1.15")
addSbtPlugin("org.xerial.sbt" % "sbt-sonatype" % "2.0")
@@ -6,4 +7,6 @@ addSbtPlugin("com.jsuereth" % "sbt-pgp" % "1.1.0")
addSbtPlugin("com.github.gseitz" % "sbt-release" % "1.0.7")
-addSbtPlugin("com.updateimpact" % "updateimpact-sbt-plugin" % "2.1.3") \ No newline at end of file
+addSbtPlugin("com.updateimpact" % "updateimpact-sbt-plugin" % "2.1.3")
+
+addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
diff --git a/test-server/src/main/resources/binaryfile.jpg b/test-server/src/main/resources/binaryfile.jpg
new file mode 100644
index 0000000..b9f5c5a
--- /dev/null
+++ b/test-server/src/main/resources/binaryfile.jpg
Binary files differ
diff --git a/test-server/src/main/resources/textfile.txt b/test-server/src/main/resources/textfile.txt
new file mode 100644
index 0000000..9904f90
--- /dev/null
+++ b/test-server/src/main/resources/textfile.txt
@@ -0,0 +1,100 @@
+- Lorem ipsum dolor sit amet
+- Vivamus sem ipsum.
+- Ut molestie.
+- Donec.
+- Fusce non porta.
+- Nulla ac metus. Morbi mattis.
+- Etiam varius.
+- Nulla.
+- Phasellus id mollis.
+- Suspendisse at.
+- Quisque nec leo velit.
+- Fusce.
+- Maecenas nec tristique senectus et.
+- Integer vestibulum lorem fermentum.
+- Vestibulum consectetuer dolor.
+- Lorem ipsum in.
+- Fusce condimentum auctor scelerisque, wisi.
+- Quisque.
+- Curae.
+- Curae, Nullam.
+- Curae, Integer.
+- Vestibulum dignissim massa. Donec.
+- Pellentesque sed sem.
+- Vivamus est.
+- Maecenas elit sed est.
+- Quisque sed tellus.
+- Cum sociis natoque penatibus et.
+- Fusce aliquam.
+- Donec.
+- Sed elementum, sapien accumsan odio.
+- Nam mattis, magna lectus, tincidunt.
+- Pellentesque scelerisque a, sodales.
+- Sed sed condimentum.
+- Curae, In nonummy. Phasellus adipiscing.
+- Vestibulum quis diam mollis.
+- Sed eros. Duis ipsum.
+- Aenean pellentesque at, mollis tempus.
+- Cras ornare facilisis sodales. Aenean.
+- Cum sociis natoque penatibus.
+- Donec id nulla.
+- Quisque ut sapien.
+- Phasellus purus. Proin ultricies.
+- Aliquam auctor neque. Nunc.
+- Nam nunc fringilla non, vehicula.
+- Morbi molestie, felis ut lobortis.
+- Nulla quis.
+- In.
+- Phasellus laoreet urna.
+- Lorem ipsum.
+- Phasellus.
+- Class aptent taciti sociosqu ad.
+- Sed lacinia.
+- Pellentesque dapibus diam. Duis.
+- Suspendisse est. Curabitur.
+- Fusce condimentum justo.
+- Aenean congue quis, faucibus.
+- Ut pharetra leo. Donec.
+- Fusce.
+- Donec porta.
+- Pellentesque orci.
+- Sed.
+- Quisque rutrum, wisi vulputate wisi.
+- Cum sociis.
+- Cras.
+- Sed eros. Curabitur.
+- Proin in velit wisi, tempor.
+- Quisque eu.
+- Proin.
+- Nam pellentesque sed, imperdiet aliquam.
+- Mauris euismod. Sed euismod orci.
+- Etiam.
+- Donec.
+- Fusce wisi a metus. Proin.
+- Phasellus quis.
+- Donec non imperdiet.
+- Aenean vel bibendum a, laoreet.
+- Fusce non enim. Phasellus vulputate.
+- Donec urna elit, sit.
+- Pellentesque habitant morbi.
+- Nulla ante. Curabitur elit. Donec.
+- Cum sociis natoque penatibus.
+- Maecenas eget leo at.
+- Cum sociis natoque penatibus et.
+- Vivamus lacus.
+- Integer.
+- Curae.
+- Maecenas rhoncus. Morbi.
+- Aenean posuere.
+- Duis.
+- Suspendisse a odio fermentum libero.
+- Nam enim. Fusce enim. In.
+- Maecenas.
+- Lorem ipsum primis.
+- Curabitur ac turpis semper sed.
+- Quisque condimentum. Donec sit.
+- Integer convallis non, posuere.
+- Etiam vulputate, odio.
+- Proin id lorem. Donec quis.
+- Curae, Sed nec augue.
+- Aliquam ut turpis. \ No newline at end of file
diff --git a/test-server/src/main/scala/com/softwaremill/sttp/server/TestHttpServer.scala b/test-server/src/main/scala/com/softwaremill/sttp/server/TestHttpServer.scala
new file mode 100644
index 0000000..906154d
--- /dev/null
+++ b/test-server/src/main/scala/com/softwaremill/sttp/server/TestHttpServer.scala
@@ -0,0 +1,197 @@
+package com.softwaremill.sttp.server
+
+import akka.actor.ActorSystem
+import akka.http.scaladsl.Http
+import akka.http.scaladsl.coding.{Deflate, Gzip, NoCoding}
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.model.headers.CacheDirectives._
+import akka.http.scaladsl.model.headers._
+import akka.http.scaladsl.server.Directives.{entity, path, _}
+import akka.http.scaladsl.server.Route
+import akka.http.scaladsl.server.directives.Credentials
+import akka.stream.ActorMaterializer
+import akka.util.ByteString
+
+import scala.concurrent.duration._
+import scala.concurrent.{Await, Future}
+
+object TestHttpServer {
+
+ def main(args: Array[String]): Unit = {
+ val port = args.headOption.map(_.toInt).getOrElse(51823)
+
+ Await.result(new TestHttpServer(port).start(), 10.seconds)
+ }
+}
+
+class TestHttpServer(port: Int) extends AutoCloseable {
+
+ import scala.concurrent.ExecutionContext.Implicits.global
+
+ private implicit val actorSystem: ActorSystem = ActorSystem("sttp-test-server")
+ private implicit val materializer: ActorMaterializer = ActorMaterializer()
+
+ private def paramsToString(m: Map[String, String]): String =
+ m.toList.sortBy(_._1).map(p => s"${p._1}=${p._2}").mkString(" ")
+
+ private val textFile = new java.io.File("src/main/resources/textfile.txt")
+ private val binaryFile = new java.io.File("src/main/resources/binaryfile.jpg")
+ private val textWithSpecialCharacters = "Żółć!"
+
+ val serverRoutes: Route =
+ pathPrefix("echo") {
+ pathPrefix("form_params") {
+ formFieldMap { params =>
+ path("as_string") {
+ complete(paramsToString(params))
+ } ~
+ path("as_params") {
+ complete(FormData(params))
+ }
+ }
+ } ~ get {
+ parameterMap { params =>
+ complete(List("GET", "/echo", paramsToString(params))
+ .filter(_.nonEmpty)
+ .mkString(" "))
+ }
+ } ~
+ post {
+ parameterMap { params =>
+ entity(as[String]) { body: String =>
+ complete(List("POST", "/echo", paramsToString(params), body)
+ .filter(_.nonEmpty)
+ .mkString(" "))
+ }
+ }
+ }
+ } ~ pathPrefix("streaming") {
+ path("echo") {
+ post {
+ parameterMap { _ =>
+ entity(as[String]) { body: String =>
+ complete(body)
+ }
+ }
+ }
+ }
+ } ~ path("set_headers") {
+ get {
+ respondWithHeader(`Cache-Control`(`max-age`(1000L))) {
+ respondWithHeader(`Cache-Control`(`no-cache`)) {
+ complete("ok")
+ }
+ }
+ }
+ } ~ pathPrefix("set_cookies") {
+ path("with_expires") {
+ setCookie(HttpCookie("c", "v", expires = Some(DateTime(1997, 12, 8, 12, 49, 12)))) {
+ complete("ok")
+ }
+ } ~ get {
+ setCookie(
+ HttpCookie(
+ "cookie1",
+ "value1",
+ secure = true,
+ httpOnly = true,
+ maxAge = Some(123L)
+ )
+ ) {
+ setCookie(HttpCookie("cookie2", "value2")) {
+ setCookie(
+ HttpCookie(
+ "cookie3",
+ "",
+ domain = Some("xyz"),
+ path = Some("a/b/c")
+ )
+ ) {
+ complete("ok")
+ }
+ }
+ }
+ }
+ } ~ path("secure_basic") {
+ authenticateBasic("test realm", {
+ case c @ Credentials.Provided(un) if un == "adam" && c.verify("1234") =>
+ Some(un)
+ case _ => None
+ }) { userName =>
+ complete(s"Hello, $userName!")
+ }
+ } ~ path("compress") {
+ encodeResponseWith(Gzip, Deflate, NoCoding) {
+ complete("I'm compressed!")
+ }
+ } ~ pathPrefix("download") {
+ path("binary") {
+ getFromFile(binaryFile)
+ } ~ path("text") {
+ getFromFile(textFile)
+ }
+ } ~ pathPrefix("multipart") {
+ entity(as[akka.http.scaladsl.model.Multipart.FormData]) { fd =>
+ complete {
+ fd.parts
+ .mapAsync(1) { p =>
+ val fv = p.entity.dataBytes.runFold(ByteString())(_ ++ _)
+ fv.map(_.utf8String)
+ .map(v => p.name + "=" + v + p.filename.fold("")(fn => s" ($fn)"))
+ }
+ .runFold(Vector.empty[String])(_ :+ _)
+ .map(v => v.mkString(", "))
+ }
+ }
+ } ~ pathPrefix("redirect") {
+ path("r1") {
+ redirect("/redirect/r2", StatusCodes.TemporaryRedirect)
+ } ~
+ path("r2") {
+ redirect("/redirect/r3", StatusCodes.PermanentRedirect)
+ } ~
+ path("r3") {
+ redirect("/redirect/r4", StatusCodes.Found)
+ } ~
+ path("r4") {
+ complete("819")
+ } ~
+ path("loop") {
+ redirect("/redirect/loop", StatusCodes.Found)
+ }
+ } ~ pathPrefix("timeout") {
+ complete {
+ akka.pattern.after(1.second, using = actorSystem.scheduler)(
+ Future.successful("Done")
+ )
+ }
+ } ~ path("empty_unauthorized_response") {
+ post {
+ import akka.http.scaladsl.model._
+ complete(
+ HttpResponse(
+ status = StatusCodes.Unauthorized,
+ headers = Nil,
+ entity = HttpEntity.Empty,
+ protocol = HttpProtocols.`HTTP/1.1`
+ ))
+ }
+ } ~ path("respond_with_iso_8859_2") {
+ get { ctx =>
+ val entity =
+ HttpEntity(MediaTypes.`text/plain`.withCharset(HttpCharset.custom("ISO-8859-2")), textWithSpecialCharacters)
+ ctx.complete(HttpResponse(200, entity = entity))
+ }
+ }
+
+ def start(): Future[Http.ServerBinding] = {
+ Http().bindAndHandle(serverRoutes, "localhost", port)
+ }
+
+ def close(): Unit = {
+ Await.result(
+ actorSystem.terminate(),
+ 10.seconds
+ )
+ }
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala b/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
deleted file mode 100644
index 55e21b8..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/BasicTests.scala
+++ /dev/null
@@ -1,702 +0,0 @@
-package com.softwaremill.sttp
-
-import java.io.{ByteArrayInputStream, IOException}
-import java.nio.ByteBuffer
-import java.nio.file.Paths
-import java.time.{ZoneId, ZonedDateTime}
-
-import akka.http.scaladsl.coding.{Deflate, Gzip, NoCoding}
-import akka.http.scaladsl.model._
-import akka.http.scaladsl.model.headers.CacheDirectives._
-import akka.http.scaladsl.model.headers._
-import akka.http.scaladsl.server.Directives._
-import akka.http.scaladsl.server.Route
-import akka.http.scaladsl.server.directives.Credentials
-import akka.util.ByteString
-import better.files._
-import com.softwaremill.sttp.akkahttp.AkkaHttpBackend
-import com.softwaremill.sttp.asynchttpclient.cats.AsyncHttpClientCatsBackend
-import com.softwaremill.sttp.asynchttpclient.future.AsyncHttpClientFutureBackend
-import com.softwaremill.sttp.asynchttpclient.monix.AsyncHttpClientMonixBackend
-import com.softwaremill.sttp.asynchttpclient.scalaz.AsyncHttpClientScalazBackend
-import com.softwaremill.sttp.impl.cats.convertCatsIOToFuture
-import com.softwaremill.sttp.impl.monix.convertMonixTaskToFuture
-import com.softwaremill.sttp.impl.scalaz.convertScalazTaskToFuture
-import com.softwaremill.sttp.okhttp.monix.OkHttpMonixBackend
-import com.softwaremill.sttp.okhttp.{OkHttpFutureBackend, OkHttpSyncBackend}
-import com.softwaremill.sttp.testing.streaming.ConvertToFuture
-import com.typesafe.scalalogging.StrictLogging
-import org.scalatest.concurrent.{IntegrationPatience, ScalaFutures}
-import org.scalatest.{path => _, _}
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.Future
-import scala.concurrent.duration._
-import scala.language.higherKinds
-
-class BasicTests
- extends FlatSpec
- with Matchers
- with BeforeAndAfterAll
- with ScalaFutures
- with OptionValues
- with StrictLogging
- with IntegrationPatience
- with TestHttpServer
- with ForceWrapped
- with BeforeAndAfterEach {
-
- override def afterEach() {
- val file = File(outPath)
- if (file.exists) file.delete()
- }
-
- private def paramsToString(m: Map[String, String]): String =
- m.toList.sortBy(_._1).map(p => s"${p._1}=${p._2}").mkString(" ")
-
- private val textFile =
- new java.io.File("tests/src/test/resources/textfile.txt")
- private val binaryFile =
- new java.io.File("tests/src/test/resources/binaryfile.jpg")
- private val outPath = Paths.get("out")
- private val textWithSpecialCharacters = "Żółć!"
-
- override val serverRoutes: Route =
- pathPrefix("echo") {
- pathPrefix("form_params") {
- formFieldMap { params =>
- path("as_string") {
- complete(paramsToString(params))
- } ~
- path("as_params") {
- complete(FormData(params))
- }
- }
- } ~ get {
- parameterMap { params =>
- complete(List("GET", "/echo", paramsToString(params))
- .filter(_.nonEmpty)
- .mkString(" "))
- }
- } ~
- post {
- parameterMap { params =>
- entity(as[String]) { body: String =>
- complete(List("POST", "/echo", paramsToString(params), body)
- .filter(_.nonEmpty)
- .mkString(" "))
- }
- }
- }
- } ~ path("set_headers") {
- get {
- respondWithHeader(`Cache-Control`(`max-age`(1000L))) {
- respondWithHeader(`Cache-Control`(`no-cache`)) {
- complete("ok")
- }
- }
- }
- } ~ pathPrefix("set_cookies") {
- path("with_expires") {
- setCookie(HttpCookie("c", "v", expires = Some(DateTime(1997, 12, 8, 12, 49, 12)))) {
- complete("ok")
- }
- } ~ get {
- setCookie(HttpCookie("cookie1", "value1", secure = true, httpOnly = true, maxAge = Some(123L))) {
- setCookie(HttpCookie("cookie2", "value2")) {
- setCookie(HttpCookie("cookie3", "", domain = Some("xyz"), path = Some("a/b/c"))) {
- complete("ok")
- }
- }
- }
- }
- } ~ path("secure_basic") {
- authenticateBasic("test realm", {
- case c @ Credentials.Provided(un) if un == "adam" && c.verify("1234") =>
- Some(un)
- case _ => None
- }) { userName =>
- complete(s"Hello, $userName!")
- }
- } ~ path("compress") {
- encodeResponseWith(Gzip, Deflate, NoCoding) {
- complete("I'm compressed!")
- }
- } ~ pathPrefix("download") {
- path("binary") {
- getFromFile(binaryFile)
- } ~ path("text") {
- getFromFile(textFile)
- }
- } ~ pathPrefix("multipart") {
- entity(as[akka.http.scaladsl.model.Multipart.FormData]) { fd =>
- complete {
- fd.parts
- .mapAsync(1) { p =>
- val fv = p.entity.dataBytes.runFold(ByteString())(_ ++ _)
- fv.map(_.utf8String)
- .map(v => p.name + "=" + v + p.filename.fold("")(fn => s" ($fn)"))
- }
- .runFold(Vector.empty[String])(_ :+ _)
- .map(v => v.mkString(", "))
- }
- }
- } ~ pathPrefix("redirect") {
- path("r1") {
- redirect("/redirect/r2", StatusCodes.TemporaryRedirect)
- } ~
- path("r2") {
- redirect("/redirect/r3", StatusCodes.PermanentRedirect)
- } ~
- path("r3") {
- redirect("/redirect/r4", StatusCodes.Found)
- } ~
- path("r4") {
- complete("819")
- } ~
- path("loop") {
- redirect("/redirect/loop", StatusCodes.Found)
- }
- } ~ pathPrefix("timeout") {
- complete {
- akka.pattern.after(1.second, using = actorSystem.scheduler)(Future.successful("Done"))
- }
- } ~ path("empty_unauthorized_response") {
- post {
- import akka.http.scaladsl.model._
- complete(
- HttpResponse(
- status = StatusCodes.Unauthorized,
- headers = Nil,
- entity = HttpEntity.Empty,
- protocol = HttpProtocols.`HTTP/1.1`
- ))
- }
- } ~ path("respond_with_iso_8859_2") {
- get { ctx =>
- val entity =
- HttpEntity(MediaTypes.`text/plain`.withCharset(HttpCharset.custom("ISO-8859-2")), textWithSpecialCharacters)
- ctx.complete(HttpResponse(200, entity = entity))
- }
- }
-
- override def port = 51823
-
- var closeBackends: List[() => Unit] = Nil
-
- runTests("HttpURLConnection")(HttpURLConnectionBackend(), ConvertToFuture.id)
- runTests("TryHttpURLConnection")(TryHttpURLConnectionBackend(), ConvertToFuture.scalaTry)
- runTests("Akka HTTP")(AkkaHttpBackend.usingActorSystem(actorSystem), ConvertToFuture.future)
- runTests("Async Http Client - Future")(AsyncHttpClientFutureBackend(), ConvertToFuture.future)
- runTests("Async Http Client - Scalaz")(AsyncHttpClientScalazBackend(), convertScalazTaskToFuture)
- runTests("Async Http Client - Monix")(AsyncHttpClientMonixBackend(), convertMonixTaskToFuture)
- runTests("Async Http Client - Cats IO")(AsyncHttpClientCatsBackend[cats.effect.IO](), convertCatsIOToFuture)
- runTests("OkHttpSyncClientHandler")(OkHttpSyncBackend(), ConvertToFuture.id)
- runTests("OkHttpAsyncClientHandler - Future")(OkHttpFutureBackend(), ConvertToFuture.future)
- runTests("OkHttpAsyncClientHandler - Monix")(OkHttpMonixBackend(), convertMonixTaskToFuture)
-
- def runTests[R[_]](name: String)(implicit
- backend: SttpBackend[R, Nothing],
- convertToFuture: ConvertToFuture[R]): Unit = {
-
- closeBackends = (() => backend.close()) :: closeBackends
-
- val postEcho = sttp.post(uri"$endpoint/echo")
- val testBody = "this is the body"
- val testBodyBytes = testBody.getBytes("UTF-8")
- val expectedPostEchoResponse = "POST /echo this is the body"
-
- val sttpIgnore = com.softwaremill.sttp.ignore
-
- parseResponseTests()
- parameterTests()
- bodyTests()
- headerTests()
- errorsTests()
- cookiesTests()
- authTests()
- compressionTests()
- downloadFileTests()
- multipartTests()
- redirectTests()
- timeoutTests()
- emptyResponseTests()
- encodingTests()
-
- def parseResponseTests(): Unit = {
- name should "parse response as string" in {
- val response = postEcho.body(testBody).send().force()
- response.unsafeBody should be(expectedPostEchoResponse)
- }
-
- name should "parse response as string with mapping using map" in {
- val response = postEcho
- .body(testBody)
- .response(asString.map(_.length))
- .send()
- .force()
- response.unsafeBody should be(expectedPostEchoResponse.length)
- }
-
- name should "parse response as string with mapping using mapResponse" in {
- val response = postEcho
- .body(testBody)
- .mapResponse(_.length)
- .send()
- .force()
- response.unsafeBody should be(expectedPostEchoResponse.length)
- }
-
- name should "parse response as a byte array" in {
- val response =
- postEcho.body(testBody).response(asByteArray).send().force()
- val fc = new String(response.unsafeBody, "UTF-8")
- fc should be(expectedPostEchoResponse)
- }
-
- name should "parse response as parameters" in {
- val params = List("a" -> "b", "c" -> "d", "e=" -> "&f")
- val response = sttp
- .post(uri"$endpoint/echo/form_params/as_params")
- .body(params: _*)
- .response(asParams)
- .send()
- .force()
- response.unsafeBody.toList should be(params)
- }
- }
-
- def parameterTests(): Unit = {
- name should "make a get request with parameters" in {
- val response = sttp
- .get(uri"$endpoint/echo?p2=v2&p1=v1")
- .send()
- .force()
-
- response.unsafeBody should be("GET /echo p1=v1 p2=v2")
- }
- }
-
- def bodyTests(): Unit = {
- name should "post a string" in {
- val response = postEcho.body(testBody).send().force()
- response.unsafeBody should be(expectedPostEchoResponse)
- }
-
- name should "post a byte array" in {
- val response =
- postEcho.body(testBodyBytes).send().force()
- response.unsafeBody should be(expectedPostEchoResponse)
- }
-
- name should "post an input stream" in {
- val response = postEcho
- .body(new ByteArrayInputStream(testBodyBytes))
- .send()
- .force()
- response.unsafeBody should be(expectedPostEchoResponse)
- }
-
- name should "post a byte buffer" in {
- val response = postEcho
- .body(ByteBuffer.wrap(testBodyBytes))
- .send()
- .force()
- response.unsafeBody should be(expectedPostEchoResponse)
- }
-
- name should "post a file" in {
- val f = File.newTemporaryFile().write(testBody)
- try {
- val response = postEcho.body(f.toJava).send().force()
- response.unsafeBody should be(expectedPostEchoResponse)
- } finally f.delete()
- }
-
- name should "post a path" in {
- val f = File.newTemporaryFile().write(testBody)
- try {
- val response =
- postEcho.body(f.toJava.toPath).send().force()
- response.unsafeBody should be(expectedPostEchoResponse)
- } finally f.delete()
- }
-
- name should "post form data" in {
- val response = sttp
- .post(uri"$endpoint/echo/form_params/as_string")
- .body("a" -> "b", "c" -> "d")
- .send()
- .force()
- response.unsafeBody should be("a=b c=d")
- }
-
- name should "post form data with special characters" in {
- val response = sttp
- .post(uri"$endpoint/echo/form_params/as_string")
- .body("a=" -> "/b", "c:" -> "/d")
- .send()
- .force()
- response.unsafeBody should be("a==/b c:=/d")
- }
-
- name should "post without a body" in {
- val response = postEcho.send().force()
- response.unsafeBody should be("POST /echo")
- }
- }
-
- def headerTests(): Unit = {
- val getHeaders = sttp.get(uri"$endpoint/set_headers")
-
- name should "read response headers" in {
- val response = getHeaders.response(sttpIgnore).send().force()
- response.headers should have length (6)
- response.headers("Cache-Control").toSet should be(Set("no-cache", "max-age=1000"))
- response.header("Server") should be('defined)
- response.header("server") should be('defined)
- response.header("Server").get should startWith("akka-http")
- response.contentType should be(Some("text/plain; charset=UTF-8"))
- response.contentLength should be(Some(2L))
- }
- }
-
- def errorsTests(): Unit = {
- val getHeaders = sttp.post(uri"$endpoint/set_headers")
-
- name should "return 405 when method not allowed" in {
- val response = getHeaders.response(sttpIgnore).send().force()
- response.code should be(405)
- response.isClientError should be(true)
- response.body should be('left)
- }
- }
-
- def cookiesTests(): Unit = {
- name should "read response cookies" in {
- val response =
- sttp
- .get(uri"$endpoint/set_cookies")
- .response(sttpIgnore)
- .send()
- .force()
- response.cookies should have length (3)
- response.cookies.toSet should be(
- Set(
- Cookie("cookie1", "value1", secure = true, httpOnly = true, maxAge = Some(123L)),
- Cookie("cookie2", "value2"),
- Cookie("cookie3", "", domain = Some("xyz"), path = Some("a/b/c"))
- ))
- }
-
- name should "read response cookies with the expires attribute" in {
- val response = sttp
- .get(uri"$endpoint/set_cookies/with_expires")
- .response(sttpIgnore)
- .send()
- .force()
- response.cookies should have length (1)
- val c = response.cookies(0)
-
- c.name should be("c")
- c.value should be("v")
- c.expires.map(_.toInstant.toEpochMilli) should be(
- Some(
- ZonedDateTime
- .of(1997, 12, 8, 12, 49, 12, 0, ZoneId.of("GMT"))
- .toInstant
- .toEpochMilli
- ))
- }
- }
-
- def authTests(): Unit = {
- val secureBasic = sttp.get(uri"$endpoint/secure_basic")
-
- name should "return a 401 when authorization fails" in {
- val req = secureBasic
- val resp = req.send().force()
- resp.code should be(401)
- resp.header("WWW-Authenticate") should be(Some("""Basic realm="test realm",charset=UTF-8"""))
- }
-
- name should "perform basic authorization" in {
- val req = secureBasic.auth.basic("adam", "1234")
- val resp = req.send().force()
- resp.code should be(200)
- resp.unsafeBody should be("Hello, adam!")
- }
- }
-
- def compressionTests(): Unit = {
- val compress = sttp.get(uri"$endpoint/compress")
- val decompressedBody = "I'm compressed!"
-
- name should "decompress using the default accept encoding header" in {
- val req = compress
- val resp = req.send().force()
- resp.unsafeBody should be(decompressedBody)
- }
-
- name should "decompress using gzip" in {
- val req =
- compress.header("Accept-Encoding", "gzip", replaceExisting = true)
- val resp = req.send().force()
- resp.unsafeBody should be(decompressedBody)
- }
-
- name should "decompress using deflate" in {
- val req =
- compress.header("Accept-Encoding", "deflate", replaceExisting = true)
- val resp = req.send().force()
- resp.unsafeBody should be(decompressedBody)
- }
-
- name should "work despite providing an unsupported encoding" in {
- val req =
- compress.header("Accept-Encoding", "br", replaceExisting = true)
- val resp = req.send().force()
- resp.unsafeBody should be(decompressedBody)
- }
- }
-
- def downloadFileTests(): Unit = {
- import CustomMatchers._
-
- name should "download a binary file using asFile" in {
- val file = outPath.resolve("binaryfile.jpg").toFile
- val req =
- sttp.get(uri"$endpoint/download/binary").response(asFile(file))
- val resp = req.send().force()
-
- resp.unsafeBody shouldBe file
- file should exist
- file should haveSameContentAs(binaryFile)
- }
-
- name should "download a text file using asFile" in {
- val file = outPath.resolve("textfile.txt").toFile
- val req =
- sttp.get(uri"$endpoint/download/text").response(asFile(file))
- val resp = req.send().force()
-
- resp.unsafeBody shouldBe file
- file should exist
- file should haveSameContentAs(textFile)
- }
-
- name should "download a binary file using asPath" in {
- val path = outPath.resolve("binaryfile.jpg")
- val req =
- sttp.get(uri"$endpoint/download/binary").response(asPath(path))
- val resp = req.send().force()
-
- resp.unsafeBody shouldBe path
- path.toFile should exist
- path.toFile should haveSameContentAs(binaryFile)
- }
-
- name should "download a text file using asPath" in {
- val path = outPath.resolve("textfile.txt")
- val req =
- sttp.get(uri"$endpoint/download/text").response(asPath(path))
- val resp = req.send().force()
-
- resp.unsafeBody shouldBe path
- path.toFile should exist
- path.toFile should haveSameContentAs(textFile)
- }
-
- name should "fail at trying to save file to a restricted location" in {
- val path = Paths.get("/").resolve("textfile.txt")
- val req =
- sttp.get(uri"$endpoint/download/text").response(asPath(path))
- val caught = intercept[IOException] {
- req.send().force()
- }
-
- caught.getMessage shouldBe "Permission denied"
- }
-
- name should "fail when file exists and overwrite flag is false" in {
- val path = outPath.resolve("textfile.txt")
- path.toFile.getParentFile.mkdirs()
- path.toFile.createNewFile()
- val req =
- sttp.get(uri"$endpoint/download/text").response(asPath(path))
-
- val caught = intercept[IOException] {
- req.send().force()
- }
-
- caught.getMessage shouldBe s"File ${path.toFile.getAbsolutePath} exists - overwriting prohibited"
-
- }
-
- name should "not fail when file exists and overwrite flag is true" in {
- val path = outPath.resolve("textfile.txt")
- path.toFile.getParentFile.mkdirs()
- path.toFile.createNewFile()
- val req =
- sttp
- .get(uri"$endpoint/download/text")
- .response(asPath(path, overwrite = true))
- val resp = req.send().force()
-
- resp.unsafeBody shouldBe path
- path.toFile should exist
- path.toFile should haveSameContentAs(textFile)
- }
- }
-
- def multipartTests(): Unit = {
- val mp = sttp.post(uri"$endpoint/multipart")
-
- name should "send a multipart message" in {
- val req = mp.multipartBody(multipart("p1", "v1"), multipart("p2", "v2"))
- val resp = req.send().force()
- resp.unsafeBody should be("p1=v1, p2=v2")
- }
-
- name should "send a multipart message with filenames" in {
- val req = mp.multipartBody(multipart("p1", "v1").fileName("f1"), multipart("p2", "v2").fileName("f2"))
- val resp = req.send().force()
- resp.unsafeBody should be("p1=v1 (f1), p2=v2 (f2)")
- }
-
- name should "send a multipart message with a file" in {
- val f = File.newTemporaryFile().write(testBody)
- try {
- val req =
- mp.multipartBody(multipart("p1", f.toJava), multipart("p2", "v2"))
- val resp = req.send().force()
- resp.unsafeBody should be(s"p1=$testBody (${f.name}), p2=v2")
- } finally f.delete()
- }
- }
-
- def redirectTests(): Unit = {
- val r1 = sttp.post(uri"$endpoint/redirect/r1")
- val r2 = sttp.post(uri"$endpoint/redirect/r2")
- val r3 = sttp.post(uri"$endpoint/redirect/r3")
- val r4response = "819"
- val loop = sttp.post(uri"$endpoint/redirect/loop")
-
- name should "not redirect when redirects shouldn't be followed (temporary)" in {
- val resp = r1.followRedirects(false).send().force()
- resp.code should be(307)
- resp.body should be('left)
- resp.history should be('empty)
- }
-
- name should "not redirect when redirects shouldn't be followed (permanent)" in {
- val resp = r2.followRedirects(false).send().force()
- resp.code should be(308)
- resp.body should be('left)
- }
-
- name should "redirect when redirects should be followed" in {
- val resp = r2.send().force()
- resp.code should be(200)
- resp.unsafeBody should be(r4response)
- }
-
- name should "redirect twice when redirects should be followed" in {
- val resp = r1.send().force()
- resp.code should be(200)
- resp.unsafeBody should be(r4response)
- }
-
- name should "redirect when redirects should be followed, and the response is parsed" in {
- val resp = r2.response(asString.map(_.toInt)).send().force()
- resp.code should be(200)
- resp.unsafeBody should be(r4response.toInt)
- }
-
- name should "keep a single history entry of redirect responses" in {
- val resp = r3.send().force()
- resp.code should be(200)
- resp.unsafeBody should be(r4response)
- resp.history should have size (1)
- resp.history(0).code should be(302)
- }
-
- name should "keep whole history of redirect responses" in {
- val resp = r1.send().force()
- resp.code should be(200)
- resp.unsafeBody should be(r4response)
- resp.history should have size (3)
- resp.history(0).code should be(307)
- resp.history(1).code should be(308)
- resp.history(2).code should be(302)
- }
-
- name should "break redirect loops" in {
- val resp = loop.send().force()
- resp.code should be(0)
- resp.history should have size (FollowRedirectsBackend.MaxRedirects)
- }
-
- name should "break redirect loops after user-specified count" in {
- val maxRedirects = 10
- val resp = loop.maxRedirects(maxRedirects).send().force()
- resp.code should be(0)
- resp.history should have size (maxRedirects)
- }
-
- name should "not redirect when maxRedirects is less than or equal to 0" in {
- val resp = loop.maxRedirects(-1).send().force()
- resp.code should be(302)
- resp.body should be('left)
- resp.history should be('empty)
- }
- }
-
- def timeoutTests(): Unit = {
- name should "fail if read timeout is not big enough" in {
- val request = sttp
- .get(uri"$endpoint/timeout")
- .readTimeout(200.milliseconds)
- .response(asString)
-
- intercept[Throwable] {
- request.send().force()
- }
- }
-
- name should "not fail if read timeout is big enough" in {
- val request = sttp
- .get(uri"$endpoint/timeout")
- .readTimeout(5.seconds)
- .response(asString)
-
- request.send().force().unsafeBody should be("Done")
- }
- }
-
- def emptyResponseTests(): Unit = {
- val postEmptyResponse = sttp
- .post(uri"$endpoint/empty_unauthorized_response")
- .body("{}")
- .contentType("application/json")
-
- name should "parse an empty error response as empty string" in {
- val response = postEmptyResponse.send().force()
- response.body should be(Left(""))
- }
- }
-
- def encodingTests(): Unit = {
- name should "read response body encoded using ISO-8859-2, as specified in the header, overriding the default" in {
- val request = sttp.get(uri"$endpoint/respond_with_iso_8859_2")
-
- request.send().force().unsafeBody should be(textWithSpecialCharacters)
- }
- }
- }
-
- override protected def afterAll(): Unit = {
- closeBackends.foreach(_())
- super.afterAll()
- }
-}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/EvalScala.scala b/tests/src/test/scala/com/softwaremill/sttp/EvalScala.scala
new file mode 100644
index 0000000..bac1537
--- /dev/null
+++ b/tests/src/test/scala/com/softwaremill/sttp/EvalScala.scala
@@ -0,0 +1,11 @@
+package com.softwaremill.sttp
+
+object EvalScala {
+ import scala.tools.reflect.ToolBox
+
+ def apply(code: String): Any = {
+ val m = scala.reflect.runtime.currentMirror
+ val tb = m.mkToolBox()
+ tb.eval(tb.parse(code))
+ }
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/HttpURLConnectionHttpTest.scala b/tests/src/test/scala/com/softwaremill/sttp/HttpURLConnectionHttpTest.scala
new file mode 100644
index 0000000..599913f
--- /dev/null
+++ b/tests/src/test/scala/com/softwaremill/sttp/HttpURLConnectionHttpTest.scala
@@ -0,0 +1,9 @@
+package com.softwaremill.sttp
+
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+class HttpURLConnectionHttpTest extends HttpTest[Id] {
+
+ override implicit val backend: SttpBackend[Id, Nothing] = HttpURLConnectionBackend()
+ override implicit val convertToFuture: ConvertToFuture[Id] = ConvertToFuture.id
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/StreamingTests.scala b/tests/src/test/scala/com/softwaremill/sttp/StreamingTests.scala
deleted file mode 100644
index b2b44a2..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/StreamingTests.scala
+++ /dev/null
@@ -1,87 +0,0 @@
-package com.softwaremill.sttp
-
-import akka.http.scaladsl.server.Directives._
-import akka.http.scaladsl.server.Route
-import com.softwaremill.sttp.streaming._
-import com.softwaremill.sttp.testing.streaming.TestStreamingBackend
-import com.typesafe.scalalogging.StrictLogging
-import org.scalatest.{BeforeAndAfterAll, FlatSpec, Matchers}
-
-import scala.language.higherKinds
-
-class StreamingTests
- extends FlatSpec
- with Matchers
- with BeforeAndAfterAll
- with StrictLogging
- with TestHttpServer
- with ForceWrapped {
-
- override val serverRoutes: Route =
- path("echo") {
- post {
- parameterMap { _ =>
- entity(as[String]) { body: String =>
- complete(body)
- }
- }
- }
- }
-
- override def port = 51824
-
- val body = "streaming test"
-
- var closeBackends: List[() => Unit] = Nil
-
- runTests("Akka Http", new AkkaHttpStreamingTests(actorSystem))
- runTests("Monix Async Http Client", new AsyncHttpClientMonixStreamingTests)
- runTests("Monix OkHttp", new OkHttpMonixStreamingTests)
- runTests("fs2 Async Http Client", new AsyncHttpClientFs2StreamingTests)
-
- def runTests[R[_], S](name: String, testStreamingBackend: TestStreamingBackend[R, S]): Unit = {
- import testStreamingBackend._
-
- closeBackends = (() => backend.close()) :: closeBackends
-
- name should "stream request body" in {
- val response = sttp
- .post(uri"$endpoint/echo")
- .streamBody(bodyProducer(body))
- .send()
- .force()
-
- response.unsafeBody shouldBe body
- }
-
- it should "receive a stream" in {
- val response = sttp
- .post(uri"$endpoint/echo")
- .body(body)
- .response(asStream[S])
- .send()
- .force()
-
- bodyConsumer(response.unsafeBody).force() shouldBe body
- }
-
- it should "receive a stream from an https site" in {
- val response = sttp
- // of course, you should never rely on the internet being available
- // in tests, but that's so much easier than setting up an https
- // testing server
- .get(uri"https://softwaremill.com")
- .response(asStream[S])
- .send()
- .force()
-
- bodyConsumer(response.unsafeBody).force() should include("</div>")
- }
- }
-
- override protected def afterAll(): Unit = {
- closeBackends.foreach(_())
- super.afterAll()
- }
-
-}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/TryHttpURLConnectionHttpTest.scala b/tests/src/test/scala/com/softwaremill/sttp/TryHttpURLConnectionHttpTest.scala
new file mode 100644
index 0000000..19b48fd
--- /dev/null
+++ b/tests/src/test/scala/com/softwaremill/sttp/TryHttpURLConnectionHttpTest.scala
@@ -0,0 +1,11 @@
+package com.softwaremill.sttp
+
+import com.softwaremill.sttp.testing.{ConvertToFuture, HttpTest}
+
+import scala.util.Try
+
+class TryHttpURLConnectionHttpTest extends HttpTest[Try] {
+
+ override implicit val backend: SttpBackend[Try, Nothing] = TryHttpURLConnectionBackend()
+ override implicit val convertToFuture: ConvertToFuture[Try] = ConvertToFuture.scalaTry
+}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientFs2StreamingTests.scala b/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientFs2StreamingTests.scala
deleted file mode 100644
index 8959ccc..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientFs2StreamingTests.scala
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.softwaremill.sttp.streaming
-
-import java.nio.ByteBuffer
-
-import cats.effect._
-import cats.instances.string._
-import com.softwaremill.sttp.SttpBackend
-import com.softwaremill.sttp.asynchttpclient.fs2.AsyncHttpClientFs2Backend
-import com.softwaremill.sttp.impl.cats.convertCatsIOToFuture
-import com.softwaremill.sttp.testing.streaming.{ConvertToFuture, TestStreamingBackend}
-import fs2.{Chunk, Stream, text}
-
-class AsyncHttpClientFs2StreamingTests extends TestStreamingBackend[IO, Stream[IO, ByteBuffer]] {
-
- override implicit val backend: SttpBackend[IO, Stream[IO, ByteBuffer]] =
- AsyncHttpClientFs2Backend[IO]()
-
- override implicit val convertToFuture: ConvertToFuture[IO] = convertCatsIOToFuture
-
- override def bodyProducer(body: String): Stream[IO, ByteBuffer] =
- Stream.emits(body.getBytes("utf-8").map(b => ByteBuffer.wrap(Array(b))))
-
- override def bodyConsumer(stream: Stream[IO, ByteBuffer]): IO[String] =
- stream
- .map(bb => Chunk.array(bb.array))
- .through(text.utf8DecodeC)
- .compile
- .foldMonoid
-
-}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientMonixStreamingTests.scala b/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientMonixStreamingTests.scala
deleted file mode 100644
index faebf8b..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/streaming/AsyncHttpClientMonixStreamingTests.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.softwaremill.sttp.streaming
-
-import java.nio.ByteBuffer
-
-import com.softwaremill.sttp.SttpBackend
-import com.softwaremill.sttp.asynchttpclient.monix.AsyncHttpClientMonixBackend
-import com.softwaremill.sttp.impl.monix.MonixTestStreamingBackend
-import monix.eval.Task
-import monix.reactive.Observable
-
-class AsyncHttpClientMonixStreamingTests extends MonixTestStreamingBackend {
-
- import monix.execution.Scheduler.Implicits.global
-
- override implicit val backend: SttpBackend[Task, Observable[ByteBuffer]] =
- AsyncHttpClientMonixBackend()
-
-}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/streaming/OkHttpMonixStreamingTests.scala b/tests/src/test/scala/com/softwaremill/sttp/streaming/OkHttpMonixStreamingTests.scala
deleted file mode 100644
index 27a4517..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/streaming/OkHttpMonixStreamingTests.scala
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.softwaremill.sttp.streaming
-
-import java.nio.ByteBuffer
-
-import com.softwaremill.sttp.SttpBackend
-import com.softwaremill.sttp.impl.monix.MonixTestStreamingBackend
-import com.softwaremill.sttp.okhttp.monix.OkHttpMonixBackend
-import monix.eval.Task
-import monix.reactive.Observable
-
-class OkHttpMonixStreamingTests extends MonixTestStreamingBackend {
-
- import monix.execution.Scheduler.Implicits.global
-
- override implicit val backend: SttpBackend[Task, Observable[ByteBuffer]] =
- OkHttpMonixBackend()
-
-}
diff --git a/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala b/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala
deleted file mode 100644
index 12fe770..0000000
--- a/tests/src/test/scala/com/softwaremill/sttp/testHelpers.scala
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.softwaremill.sttp
-
-import java.nio.file.{Files, Paths}
-import java.{io, util}
-
-import akka.actor.ActorSystem
-import akka.http.scaladsl.Http
-import akka.http.scaladsl.server.Route
-import akka.stream.ActorMaterializer
-import org.scalatest.concurrent.{PatienceConfiguration, ScalaFutures}
-import org.scalatest.exceptions.TestFailedException
-import org.scalatest.matchers.{MatchResult, Matcher}
-import org.scalatest.{BeforeAndAfterAll, Suite}
-
-import scala.concurrent.duration._
-import scala.language.higherKinds
-
-trait TestHttpServer extends BeforeAndAfterAll with ScalaFutures with TestingPatience {
- this: Suite =>
- protected implicit val actorSystem: ActorSystem = ActorSystem("sttp-test")
- import actorSystem.dispatcher
-
- protected implicit val materializer = ActorMaterializer()
- protected val endpoint = uri"http://localhost:$port"
-
- override protected def beforeAll(): Unit = {
- Http().bindAndHandle(serverRoutes, "localhost", port).futureValue
- }
-
- override protected def afterAll(): Unit = {
- actorSystem.terminate().futureValue
- }
-
- def serverRoutes: Route
- def port: Int
-}
-
-trait ForceWrapped extends ScalaFutures with TestingPatience { this: Suite =>
- type ConvertToFuture[R[_]] =
- com.softwaremill.sttp.testing.streaming.ConvertToFuture[R]
-
- implicit class ForceDecorator[R[_], T](wrapped: R[T]) {
- def force()(implicit ctf: ConvertToFuture[R]): T = {
- try {
- ctf.toFuture(wrapped).futureValue
- } catch {
- case e: TestFailedException if e.getCause != null => throw e.getCause
- }
- }
- }
-}
-
-object EvalScala {
- import scala.tools.reflect.ToolBox
-
- def apply(code: String): Any = {
- val m = scala.reflect.runtime.currentMirror
- val tb = m.mkToolBox()
- tb.eval(tb.parse(code))
- }
-}
-
-object CustomMatchers {
- class FileContentsMatch(file: java.io.File) extends Matcher[java.io.File] {
- override def apply(left: io.File): MatchResult = {
- val inBA = Files.readAllBytes(Paths.get(left.getAbsolutePath))
- val expectedBA = Files.readAllBytes(Paths.get(file.getAbsolutePath))
- MatchResult(
- util.Arrays.equals(inBA, expectedBA),
- "The files' contents are not the same",
- "The files' contents are the same"
- )
- }
- }
-
- def haveSameContentAs(file: io.File) = new FileContentsMatch(file)
-}
-
-trait TestingPatience extends PatienceConfiguration {
- override implicit val patienceConfig: PatienceConfig =
- PatienceConfig(timeout = 5.seconds, interval = 150.milliseconds)
-}