diff options
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.scala | 524 |
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() + } + +} |