<commit_before>package sttp.client.testing

import java.io.File
import java.nio.file.Files
import java.security.MessageDigest
import java.time.{ZoneId, ZonedDateTime}

import com.github.ghik.silencer.silent
import sttp.client.{FollowRedirectsBackend, _}
import sttp.model.{CookieWithMeta, Header, HeaderNames, StatusCode}

import scala.concurrent.Future
import scala.language.higherKinds

@silent("discarded")
trait HttpTestExtensions[F[_]] extends TestHttpServer { self: HttpTest[F] =>
  "cookies" - {
    "read response cookies" in {
      basicRequest
        .get(uri"$endpoint/set_cookies")
        .response(sttpIgnore)
        .send()
        .toFuture()
        .map { response =>
          response.cookies should have length (3)
          response.cookies.toSet should be(
            Set(
              CookieWithMeta.unsafeApply("cookie1", "value1", secure = true, httpOnly = true, maxAge = Some(123L)),
              CookieWithMeta.unsafeApply("cookie2", "value2"),
              CookieWithMeta.unsafeApply("cookie3", "", domain = Some("xyz"), path = Some("a/b/c"))
            )
          )
        }
    }

    "read response cookies with the expires attribute" in {
      basicRequest
        .get(uri"$endpoint/set_cookies/with_expires")
        .response(sttpIgnore)
        .send()
        .toFuture()
        .map { response =>
          response.cookies should have length (1)
          val c = response.cookies(0)

          c.name should be("c")
          c.value should be("v")
          c.expires.map(_.toEpochMilli) should be(
            Some(
              ZonedDateTime
                .of(1997, 12, 8, 12, 49, 12, 0, ZoneId.of("GMT"))
                .toInstant
                .toEpochMilli
            )
          )
        }
    }
  }

  // browsers do not allow access to redirect responses
  "follow redirects" - {
    def r1 = basicRequest.post(uri"$endpoint/redirect/r1").response(asStringAlways)
    def r3 = basicRequest.post(uri"$endpoint/redirect/r3").response(asStringAlways)
    val r4response = "819"
    def loop = basicRequest.post(uri"$endpoint/redirect/loop")

    "keep a single history entry of redirect responses" in {
      r3.send().toFuture().map { resp =>
        resp.code shouldBe StatusCode.Ok
        resp.body should be(r4response)
        resp.history should have size (1)
        resp.history(0).code shouldBe StatusCode.Found
      }
    }

    "keep whole history of redirect responses" in {
      r1.send().toFuture().map { resp =>
        resp.code shouldBe StatusCode.Ok
        resp.body should be(r4response)
        resp.history should have size (3)
        resp.history(0).code shouldBe StatusCode.TemporaryRedirect
        resp.history(1).code shouldBe StatusCode.PermanentRedirect
        resp.history(2).code shouldBe StatusCode.Found
      }
    }

    "break redirect loops" in {
      // sync backends can throw exceptions when evaluating send(), before the toFuture() conversion
      Future(loop.send().toFuture()).flatMap(identity).failed.map {
        case TooManyRedirectsException(_, redirects) =>
          redirects shouldBe FollowRedirectsBackend.MaxRedirects
      }
    }

    "break redirect loops after user-specified count" in {
      val maxRedirects = 10
      Future(loop.maxRedirects(maxRedirects).send().toFuture()).flatMap(identity).failed.collect {
        case TooManyRedirectsException(_, redirects) =>
          redirects shouldBe maxRedirects
      }
    }

    val redirectToGetTestData = List(
      (301, false, "POSTx"),
      (302, false, "POSTx"),
      (303, false, "GET"),
      (307, false, "POSTx"),
      (308, false, "POSTx"),
      (301, true, "GET"),
      (302, true, "GET"),
      (303, true, "GET"),
      (307, true, "POSTx"),
      (308, true, "POSTx")
    )

    for ((statusCode, redirectToGet, expectedBody) <- redirectToGetTestData) yield {
      s"for $statusCode redirect, with redirect post to get = $redirectToGet, should return body $expectedBody" in {
        basicRequest
          .redirectToGet(redirectToGet)
          .body("x")
          .post(uri"$endpoint/redirect/get_after_post/r$statusCode")
          .response(asStringAlways)
          .send()
          .toFuture()
          .map { resp =>
            resp.body shouldBe expectedBody
          }
      }
    }

    "strip sensitive headers" - {
      val testData = List(
        Header.notValidated(HeaderNames.Authorization, "secret"),
        Header.notValidated(HeaderNames.Cookie, "A=B;"),
        Header.notValidated(HeaderNames.SetCookie, "A=B")
      )

      for (header <- testData) yield {
        s"for $header redirect" in {
          basicRequest
            .get(uri"$endpoint/redirect/strip_sensitive_headers/r1")
            .header(header)
            .response(asStringAlways)
            .send()
            .toFuture()
            .map { resp =>
              resp.body should not include header.toString
            }
        }
      }
    }
  }

  // scalajs and scala native only support US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16
  "encoding" - {
    "read response body encoded using ISO-8859-2, as specified in the header, overriding the default" in {
      val req = basicRequest.get(uri"$endpoint/respond_with_iso_8859_2")

      req.send().toFuture().map { response =>
        response.body should be(Right("Żółć!"))
      }
    }<commit_msg>Restore println as a fix for travis job timeout<commit_after>package sttp.client.testing

import java.io.File
import java.nio.file.Files
import java.security.MessageDigest
import java.time.{ZoneId, ZonedDateTime}

import com.github.ghik.silencer.silent
import sttp.client.{FollowRedirectsBackend, _}
import sttp.model.{CookieWithMeta, Header, HeaderNames, StatusCode}

import scala.concurrent.Future
import scala.language.higherKinds

@silent("discarded")
trait HttpTestExtensions[F[_]] extends TestHttpServer { self: HttpTest[F] =>
  "cookies" - {
    "read response cookies" in {
      basicRequest
        .get(uri"$endpoint/set_cookies")
        .response(sttpIgnore)
        .send()
        .toFuture()
        .map { response =>
          response.cookies should have length (3)
          response.cookies.toSet should be(
            Set(
              CookieWithMeta.unsafeApply("cookie1", "value1", secure = true, httpOnly = true, maxAge = Some(123L)),
              CookieWithMeta.unsafeApply("cookie2", "value2"),
              CookieWithMeta.unsafeApply("cookie3", "", domain = Some("xyz"), path = Some("a/b/c"))
            )
          )
        }
    }

    "read response cookies with the expires attribute" in {
      basicRequest
        .get(uri"$endpoint/set_cookies/with_expires")
        .response(sttpIgnore)
        .send()
        .toFuture()
        .map { response =>
          response.cookies should have length (1)
          val c = response.cookies(0)

          c.name should be("c")
          c.value should be("v")
          c.expires.map(_.toEpochMilli) should be(
            Some(
              ZonedDateTime
                .of(1997, 12, 8, 12, 49, 12, 0, ZoneId.of("GMT"))
                .toInstant
                .toEpochMilli
            )
          )
        }
    }
  }

  // browsers do not allow access to redirect responses
  "follow redirects" - {
    def r1 = basicRequest.post(uri"$endpoint/redirect/r1").response(asStringAlways)
    def r3 = basicRequest.post(uri"$endpoint/redirect/r3").response(asStringAlways)
    val r4response = "819"
    def loop = basicRequest.post(uri"$endpoint/redirect/loop")

    "keep a single history entry of redirect responses" in {
      r3.send().toFuture().map { resp =>
        resp.code shouldBe StatusCode.Ok
        resp.body should be(r4response)
        resp.history should have size (1)
        resp.history(0).code shouldBe StatusCode.Found
      }
    }

    "keep whole history of redirect responses" in {
      r1.send().toFuture().map { resp =>
        resp.code shouldBe StatusCode.Ok
        resp.body should be(r4response)
        resp.history should have size (3)
        resp.history(0).code shouldBe StatusCode.TemporaryRedirect
        resp.history(1).code shouldBe StatusCode.PermanentRedirect
        resp.history(2).code shouldBe StatusCode.Found
      }
    }

    "break redirect loops" in {
      // sync backends can throw exceptions when evaluating send(), before the toFuture() conversion
      Future(loop.send().toFuture()).flatMap(identity).failed.map {
        case TooManyRedirectsException(_, redirects) =>
          redirects shouldBe FollowRedirectsBackend.MaxRedirects
      }
    }

    "break redirect loops after user-specified count" in {
      val maxRedirects = 10
      Future(loop.maxRedirects(maxRedirects).send().toFuture()).flatMap(identity).failed.collect {
        case TooManyRedirectsException(_, redirects) =>
          redirects shouldBe maxRedirects
      }
    }

    val redirectToGetTestData = List(
      (301, false, "POSTx"),
      (302, false, "POSTx"),
      (303, false, "GET"),
      (307, false, "POSTx"),
      (308, false, "POSTx"),
      (301, true, "GET"),
      (302, true, "GET"),
      (303, true, "GET"),
      (307, true, "POSTx"),
      (308, true, "POSTx")
    )

    for ((statusCode, redirectToGet, expectedBody) <- redirectToGetTestData) yield {
      s"for $statusCode redirect, with redirect post to get = $redirectToGet, should return body $expectedBody" in {
        basicRequest
          .redirectToGet(redirectToGet)
          .body("x")
          .post(uri"$endpoint/redirect/get_after_post/r$statusCode")
          .response(asStringAlways)
          .send()
          .toFuture()
          .map { resp =>
            resp.body shouldBe expectedBody
          }
      }
    }

    "strip sensitive headers" - {
      val testData = List(
        Header.notValidated(HeaderNames.Authorization, "secret"),
        Header.notValidated(HeaderNames.Cookie, "A=B;"),
        Header.notValidated(HeaderNames.SetCookie, "A=B")
      )

      for (header <- testData) yield {
        s"for $header redirect" in {
          basicRequest
            .get(uri"$endpoint/redirect/strip_sensitive_headers/r1")
            .header(header)
            .response(asStringAlways)
            .send()
            .toFuture()
            .map { resp =>
              println(resp.body)
              resp.body should not include header.toString
            }
        }
      }
    }
  }

  // scalajs and scala native only support US_ASCII, ISO_8859_1, UTF_8, UTF_16BE, UTF_16LE, UTF_16
  "encoding" - {
    "read response body encoded using ISO-8859-2, as specified in the header, overriding the default" in {
      val req = basicRequest.get(uri"$endpoint/respond_with_iso_8859_2")

      req.send().toFuture().map { response =>
        response.body should be(Right("Żółć!"))
      }
    }
{'fraction_non_alphanumeric': 0.1151750253994643, 'fraction_numerical': 0.018102890920846035, 'mean_word_length': 2.3042416844675007, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3835695', 'n_tokens_mistral': 3345, 'n_tokens_neox': 3164, 'n_words': 731}