aboutsummaryrefslogtreecommitdiff
path: root/core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala')
-rw-r--r--core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala524
1 files changed, 524 insertions, 0 deletions
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..5598aa2
--- /dev/null
+++ b/core/src/test/scala/com/softwaremill/sttp/testing/HttpTest.scala
@@ -0,0 +1,524 @@
+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
+ with TestHttpServer {
+
+ override def afterEach() {
+ val file = File(outPath)
+ if (file.exists) file.delete()
+ }
+
+ private val textFile = new java.io.File(getClass.getResource("/textfile.txt").getFile)
+ private val binaryFile = new java.io.File(getClass.getResource("/binaryfile.jpg").getFile)
+ private val outPath = File.newTemporaryDirectory().path
+ private val textWithSpecialCharacters = "Żółć!"
+
+ implicit val backend: SttpBackend[R, Nothing]
+ implicit val convertToFuture: ConvertToFuture[R]
+
+ private def 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" - {
+ def 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" - {
+ def 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" - {
+ def 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" - {
+ def 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" - {
+ def 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" - {
+ def r1 = sttp.post(uri"$endpoint/redirect/r1")
+ def r2 = sttp.post(uri"$endpoint/redirect/r2")
+ def r3 = sttp.post(uri"$endpoint/redirect/r3")
+ val r4response = "819"
+ def 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" - {
+ def 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()
+ }
+
+}