From cb5cde1019c91c55a3c82fbb90dfe2ca22e687b2 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 22 May 2024 13:39:15 +0200 Subject: [PATCH 1/7] Added method for parsing string body --- core/src/main/scala/sttp/model/Uri.scala | 2 +- .../scala/sttp/model/internal/Rfc3986.scala | 23 +++++++++++++++++-- .../model/internal/UriCompatibility.scala | 6 ++--- core/src/test/scala/sttp/model/UriTests.scala | 4 ++++ 4 files changed, 29 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/sttp/model/Uri.scala b/core/src/main/scala/sttp/model/Uri.scala index abce7ab3..a409f111 100644 --- a/core/src/main/scala/sttp/model/Uri.scala +++ b/core/src/main/scala/sttp/model/Uri.scala @@ -709,7 +709,7 @@ object Uri extends UriInterpolator { object QuerySegmentEncoding { - /** Encodes all reserved characters using [[java.net.URLEncoder.encode()]]. */ + /** Encodes all reserved characters using [[sttp.model.internal.Rfc3986]] with characters from [[sttp.model.internal.Rfc3986.Unreserved]]. */ val All: Encoding = UriCompatibility.encodeQuery(_, "UTF-8") /** Encodes only the `&` and `=` reserved characters, which are usually used to separate query parameter names and diff --git a/core/src/main/scala/sttp/model/internal/Rfc3986.scala b/core/src/main/scala/sttp/model/internal/Rfc3986.scala index 3afb9ce5..9523231c 100644 --- a/core/src/main/scala/sttp/model/internal/Rfc3986.scala +++ b/core/src/main/scala/sttp/model/internal/Rfc3986.scala @@ -15,17 +15,36 @@ object Rfc3986 { val QueryWithBrackets: Set[Char] = Query ++ Set('[', ']') - /** @param spaceAsPlus + /** Encode string using UTF-8 + * @param spaceAsPlus * In the query, space is encoded as a `+`. In other contexts, it should be %-encoded as `%20`. * @param encodePlus * Should `+` (which is the encoded form of space in the query) be %-encoded. */ def encode(allowedCharacters: Set[Char], spaceAsPlus: Boolean = false, encodePlus: Boolean = false)( s: String + ): String = + encode(s, "utf-8", allowedCharacters, spaceAsPlus, encodePlus) + + + def encode(s: String, enc: String, allowedCharacters: Set[Char]): String = + encode(s, enc, allowedCharacters, spaceAsPlus = false, encodePlus = false) + + /** @param spaceAsPlus + * In the query, space is encoded as a `+`. In other contexts, it should be %-encoded as `%20`. + * @param encodePlus + * Should `+` (which is the encoded form of space in the query) be %-encoded. + */ + private def encode( + s: String, + enc: String, + allowedCharacters: Set[Char], + spaceAsPlus: Boolean, + encodePlus: Boolean ): String = { val sb = new StringBuilder() // based on https://gist.github.com/teigen/5865923 - for (b <- s.getBytes("UTF-8")) { + for (b <- s.getBytes(enc)) { val c = (b & 0xff).toChar if (c == '+' && encodePlus) sb.append("%2B") // #48 else if (allowedCharacters(c)) sb.append(c) diff --git a/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala b/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala index 0adda880..f599620a 100644 --- a/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala +++ b/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala @@ -1,7 +1,5 @@ package sttp.model.internal -import java.net.URLEncoder - private[sttp] object UriCompatibility { def encodeDNSHost(host: String): String = { val noSpecialChars = if (host.contains("..")) { @@ -18,5 +16,7 @@ private[sttp] object UriCompatibility { Rfc3986.encode(Rfc3986.Host)(noSpecialChars) } - def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc) + def encodeQuery(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) + + def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) } diff --git a/core/src/test/scala/sttp/model/UriTests.scala b/core/src/test/scala/sttp/model/UriTests.scala index 0c08e3fe..2eb62c51 100644 --- a/core/src/test/scala/sttp/model/UriTests.scala +++ b/core/src/test/scala/sttp/model/UriTests.scala @@ -116,6 +116,10 @@ class UriTests extends AnyFunSuite with Matchers with TryValues { List(QS.KeyValue("k1&", "v1&", valueEncoding = QuerySegmentEncoding.Relaxed)) -> "k1%26=v1&", List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?", List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2", + List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2", + List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2", + List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2", + List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2", List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2", List(QS.KeyValue("k", "+1234")) -> "k=%2B1234", List(QS.KeyValue("k", "[]")) -> "k=%5B%5D", From 9d896ea5d8028bea11a1caa51c88ac06d49b7f20 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Wed, 22 May 2024 13:58:26 +0200 Subject: [PATCH 2/7] Added methods for scala natvie and JS --- .../main/scalajs/sttp/model/internal/UriCompatibility.scala | 2 ++ .../scalanative/sttp/model/internal/UriCompatibility.scala | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/scalajs/sttp/model/internal/UriCompatibility.scala b/core/src/main/scalajs/sttp/model/internal/UriCompatibility.scala index e3fb4167..87c13a1e 100644 --- a/core/src/main/scalajs/sttp/model/internal/UriCompatibility.scala +++ b/core/src/main/scalajs/sttp/model/internal/UriCompatibility.scala @@ -11,4 +11,6 @@ private[sttp] object UriCompatibility { } def encodeQuery(s: String, enc: String): String = URIUtils.encodeURIComponent(s) + + def encodeBodyPart(s: String, enc: String): String = URIUtils.encodeURIComponent(s) } diff --git a/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala b/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala index 057ba56d..1fe32ba4 100644 --- a/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala +++ b/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala @@ -20,5 +20,7 @@ private[sttp] object UriCompatibility { Rfc3986.encode(Rfc3986.Host)(noSpecialChars) } - def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc) + def encodeQuery(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) + + def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) } From 8e132d947900a29d223653240ce1184eb4e6a659 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Thu, 23 May 2024 12:41:33 +0200 Subject: [PATCH 3/7] Restore URLEncoder for queries --- core/src/main/scala/sttp/model/Uri.scala | 2 +- .../scala/sttp/model/internal/Rfc3986.scala | 10 +++++++++- .../model/internal/UriCompatibility.scala | 4 +++- .../model/internal/UriCompatibility.scala | 2 +- core/src/test/scala/sttp/model/UriTests.scala | 20 +++++++++++++++++-- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/core/src/main/scala/sttp/model/Uri.scala b/core/src/main/scala/sttp/model/Uri.scala index a409f111..abce7ab3 100644 --- a/core/src/main/scala/sttp/model/Uri.scala +++ b/core/src/main/scala/sttp/model/Uri.scala @@ -709,7 +709,7 @@ object Uri extends UriInterpolator { object QuerySegmentEncoding { - /** Encodes all reserved characters using [[sttp.model.internal.Rfc3986]] with characters from [[sttp.model.internal.Rfc3986.Unreserved]]. */ + /** Encodes all reserved characters using [[java.net.URLEncoder.encode()]]. */ val All: Encoding = UriCompatibility.encodeQuery(_, "UTF-8") /** Encodes only the `&` and `=` reserved characters, which are usually used to separate query parameter names and diff --git a/core/src/main/scala/sttp/model/internal/Rfc3986.scala b/core/src/main/scala/sttp/model/internal/Rfc3986.scala index 9523231c..52f490ba 100644 --- a/core/src/main/scala/sttp/model/internal/Rfc3986.scala +++ b/core/src/main/scala/sttp/model/internal/Rfc3986.scala @@ -24,9 +24,17 @@ object Rfc3986 { def encode(allowedCharacters: Set[Char], spaceAsPlus: Boolean = false, encodePlus: Boolean = false)( s: String ): String = - encode(s, "utf-8", allowedCharacters, spaceAsPlus, encodePlus) + encode(s, "UTF-8", allowedCharacters, spaceAsPlus, encodePlus) + /** Encode string using encoding, leave allowedCharacters as they are + * @param s + * String to be encoded + * @param enc + * Encoding to be used + * @param allowedCharacters + * Characters to be not to be encoded + */ def encode(s: String, enc: String, allowedCharacters: Set[Char]): String = encode(s, enc, allowedCharacters, spaceAsPlus = false, encodePlus = false) diff --git a/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala b/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala index f599620a..62059559 100644 --- a/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala +++ b/core/src/main/scalajvm/sttp/model/internal/UriCompatibility.scala @@ -1,5 +1,7 @@ package sttp.model.internal +import java.net.URLEncoder + private[sttp] object UriCompatibility { def encodeDNSHost(host: String): String = { val noSpecialChars = if (host.contains("..")) { @@ -16,7 +18,7 @@ private[sttp] object UriCompatibility { Rfc3986.encode(Rfc3986.Host)(noSpecialChars) } - def encodeQuery(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) + def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc) def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) } diff --git a/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala b/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala index 1fe32ba4..e3291d2b 100644 --- a/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala +++ b/core/src/main/scalanative/sttp/model/internal/UriCompatibility.scala @@ -20,7 +20,7 @@ private[sttp] object UriCompatibility { Rfc3986.encode(Rfc3986.Host)(noSpecialChars) } - def encodeQuery(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) + def encodeQuery(s: String, enc: String): String = URLEncoder.encode(s, enc) def encodeBodyPart(s: String, enc: String): String = Rfc3986.encode(s, enc, Rfc3986.Unreserved) } diff --git a/core/src/test/scala/sttp/model/UriTests.scala b/core/src/test/scala/sttp/model/UriTests.scala index 2eb62c51..5f54bb05 100644 --- a/core/src/test/scala/sttp/model/UriTests.scala +++ b/core/src/test/scala/sttp/model/UriTests.scala @@ -1,11 +1,11 @@ package sttp.model import java.net.URI - import Uri._ import org.scalatest.TryValues import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import sttp.model.internal.UriCompatibility class UriTests extends AnyFunSuite with Matchers with TryValues { @@ -117,7 +117,7 @@ class UriTests extends AnyFunSuite with Matchers with TryValues { List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?", List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2", List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2", - List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2", + List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%7Ev2", List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2", List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2", List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2", @@ -134,6 +134,22 @@ class UriTests extends AnyFunSuite with Matchers with TryValues { } } + private val bodyPartEncodingTestData = List( + "v1,v2" -> "v1%2Cv2", + "v1-v2" -> "v1-v2", + "v1~v2" -> "v1~v2", + "v1_v2" -> "v1_v2", + "v1.v2" -> "v1.v2", + ) + + for { + (segments, expected) <- bodyPartEncodingTestData + } { + test(s"$segments should serialize to$expected") { + UriCompatibility.encodeBodyPart(segments, "utf-8") should endWith(expected) + } + } + val hostTestData = List( "www.mikołak.net" -> "http://www.xn--mikoak-6db.net", "192.168.1.0" -> "http://192.168.1.0", From 4ce8510c64ef7c645a68dc98dc71ff756bd5eb02 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Thu, 23 May 2024 13:19:25 +0200 Subject: [PATCH 4/7] Split JS and JVM tests --- .../test/scalajs/sttp/model/UriTests.scala | 264 ++++++++++++++++++ .../sttp/model/UriTests.scala | 5 +- 2 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 core/src/test/scalajs/sttp/model/UriTests.scala rename core/src/test/{scala => scalajvm}/sttp/model/UriTests.scala (99%) diff --git a/core/src/test/scalajs/sttp/model/UriTests.scala b/core/src/test/scalajs/sttp/model/UriTests.scala new file mode 100644 index 00000000..2dc216db --- /dev/null +++ b/core/src/test/scalajs/sttp/model/UriTests.scala @@ -0,0 +1,264 @@ +package sttp.model + +import org.scalatest.TryValues +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import sttp.model.Uri._ +import sttp.model.internal.UriCompatibility + +import java.net.URI + +class UriTests extends AnyFunSuite with Matchers with TryValues { + + val HS = HostSegment + val PS = PathSegment + val QS = QuerySegment + + val wholeUriTestData = List( + Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) -> "http://example.com", + Uri.unsafeApply("http", None, "example.com", None, List(""), Nil, None) -> "http://example.com/", + Uri.unsafeApply( + "https", + None, + "sub.example.com", + Some(8080), + List("a", "b", "xyz"), + List(QS.KeyValue("p1", "v1"), QS.KeyValue("p2", "v2")), + Some("f") + ) -> + "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f", + Uri.unsafeApply( + "http", + None, + "example.com", + None, + List(""), + List(QS.KeyValue("p", "v"), QS.KeyValue("p", "v")), + None + ) -> "http://example.com/?p=v&p=v", + Uri.unsafeApply( + "http", + None, + "exa mple.com", + None, + List("a b", "z", "ą:ę"), + List(QS.KeyValue("p:1", "v&v"), QS.KeyValue("p2", "v v")), + None + ) -> + "http://exa%20mple.com/a%20b/z/%C4%85:%C4%99?p:1=v%26v&p2=v+v", + Uri.unsafeApply("http", Some(UserInfo("us&e/r", Some("pa ss"))), "example.com", None, Nil, Nil, None) -> + "http://us&e%2Fr:pa%20ss@example.com", + Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, Some("f:g/h i")) -> + "http://example.com#f:g/h%20i", + Uri.unsafeApply("http", None, "example.com", None, List("key=value"), Nil, None) -> + "http://example.com/key=value", + Uri.unsafeApply("2001:db8::ff00:42:8329", 8080) -> "http://[2001:db8::ff00:42:8329]:8080", + Uri.unsafeApply( + "http", + Some(Authority("example.com")), + List(Segment("a b", identity)), + Nil, + None + ) -> "http://example.com/a b", + Uri.unsafeApply("http", None, Nil, Nil, None) -> "http:", + Uri.unsafeApply("mailto", List("user@example.com")) -> "mailto:user@example.com", + Uri.unsafeApply("http", "sub..domain") -> "http://sub..domain", + Uri.relative(List("x", "y")) -> "/x/y", + Uri.relative(List("x", "y", "")) -> "/x/y/", + Uri.relative(List("")) -> "/", + Uri.relative(List("x"), Some("a")) -> "/x#a", + Uri.relative(List("x"), List(QS.KeyValue("p1", "v1")), Some("a")) -> "/x?p1=v1#a", + Uri.pathRelative(List("x", "y")) -> "x/y", + Uri.pathRelative(List("..", "x", "y")) -> "../x/y" + ) + + for { + (uri, expected) <- wholeUriTestData + } { + test(s"$uri should serialize to $expected") { + uri.toString should be(expected) + } + } + + val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) + + val pathTestData = List( + "a/b/c" -> List("a", "b", "c"), + "/a/b/c" -> List("a", "b", "c"), + "/" -> List(""), + "" -> List("") + ) + + for { + (path, expected) <- pathTestData + } { + test(s"whole path: $path, should parse as: $expected") { + testUri.withWholePath(path).path.toList should be(expected) + } + } + + val querySegmentsTestData = List( + List( + QS.KeyValue("k1", "v1"), + QS.KeyValue("k2", "v2"), + QS.KeyValue("k3", "v3"), + QS.KeyValue("k4", "v4") + ) -> "k1=v1&k2=v2&k3=v3&k4=v4", + List( + QS.KeyValue("k1", "v1"), + QS.KeyValue("k2", "v2"), + QS.Plain("-abc-"), + QS.KeyValue("k3", "v3"), + QS.KeyValue("k4", "v4") + ) -> "k1=v1&k2=v2-abc-k3=v3&k4=v4", + List(QS.KeyValue("k1", "v1"), QS.Plain("&abc&"), QS.KeyValue("k2", "v2")) -> "k1=v1%26abc%26k2=v2", + List(QS.KeyValue("k1", "v1"), QS.Plain("&abc&", encoding = QuerySegmentEncoding.Relaxed)) -> "k1=v1&abc&", + List(QS.KeyValue("k1&", "v1&", keyEncoding = QuerySegmentEncoding.Relaxed)) -> "k1&=v1%26", + List(QS.KeyValue("k1&", "v1&", valueEncoding = QuerySegmentEncoding.Relaxed)) -> "k1%26=v1&", + List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?", + List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2", + List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2", + List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2", + List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2", + List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2", + List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2", + List(QS.KeyValue("k", "+1234")) -> "k=%2B1234", + List(QS.KeyValue("k", "[]")) -> "k=%5B%5D", + List(QS.KeyValue("k", "[]", valueEncoding = QuerySegmentEncoding.RelaxedWithBrackets)) -> "k=[]" + ) + + for { + (segments, expected) <- querySegmentsTestData + } { + test(s"$segments should serialize to$expected") { + testUri.copy(querySegments = segments).toString should endWith(expected) + } + } + + private val bodyPartEncodingTestData = List( + "v1,v2" -> "v1%2Cv2", + "v1-v2" -> "v1-v2", + "v1~v2" -> "v1~v2", + "v1_v2" -> "v1_v2", + "v1.v2" -> "v1.v2", + ) + + for { + (segments, expected) <- bodyPartEncodingTestData + } { + test(s"$segments should serialize to$expected") { + UriCompatibility.encodeBodyPart(segments, "utf-8") should endWith(expected) + } + } + + val hostTestData = List( + "www.mikołak.net" -> "http://www.xn--mikoak-6db.net", + "192.168.1.0" -> "http://192.168.1.0", + "::1" -> "http://[::1]", + "2001:db8::ff00:42:8329" -> "http://[2001:db8::ff00:42:8329]", + "2001:0db8:0000:0000:0000:ff00:0042:8329" -> "http://[2001:0db8:0000:0000:0000:ff00:0042:8329]" + ) + + for { + (host, expected) <- hostTestData + } { + test(s"host $host should serialize to $expected") { + Uri.unsafeApply(host).toString should be(s"$expected") + } + } + + test("should convert from java URI") { + val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" + Uri(URI.create(uriAsString)).toString should be(uriAsString) + } + + test("should parse raw string") { + val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" + Uri.parse(uriAsString).right.map(_.toString) shouldBe Right(uriAsString) + val badString = "xyz://foobar:80:37/?&?" + Uri.parse(badString).isLeft shouldBe true + } + + test("should convert to java URI") { + val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" + uri"$uriAsString".toJavaUri.toString should be(uriAsString) + } + + test("should have multi params") { + val uriAsString = "https://sub.example.com:8080?p1=v1&p2=v2&p1=v3&p2=v4" + val expectedParams = Map("p1" -> List("v1", "v3"), "p2" -> List("v2", "v4")) + uri"$uriAsString".params.toMultiMap should be(expectedParams) + } + + test("should have no multi params") { + val uriAsString = "https://sub.example.com:8080" + uri"$uriAsString".params.toMultiMap should be(Map()) + } + + test("should have non-empty multi params") { + val uriAsString = "https://sub.example.com:8080?p1&p2" + uri"$uriAsString".params.toMultiMap should be(Map("p1" -> Nil, "p2" -> Nil)) + } + + test("should have multi params with empty string values") { + val uriAsString = "https://sub.example.com:8080?p1=&p2=" + uri"$uriAsString".params.toMultiMap should be(Map("p1" -> List(""), "p2" -> List(""))) + } + + test("should have multi params with only values") { + val uriAsString = "https://sub.example.com:8080?=v1&=v2" + uri"$uriAsString".params.toMultiMap should be(Map("" -> List("v1", "v2"))) + } + + test("should replace or append path") { + uri"http://example.org/x/y".addPath("z").path shouldBe List("x", "y", "z") + uri"http://example.org/x/y".withPath("z").path shouldBe List("z") + } + + test("should replace or append query parameter") { + uri"http://example.org?x=1".addParam("y", "2").paramsMap shouldBe Map("x" -> "1", "y" -> "2") + uri"http://example.org?x=1".withParam("y", "2").paramsMap shouldBe Map("y" -> "2") + } + + test("should parse no-value parameters") { + val uriAsString = "https://sub.example.com:8080?p1&p2=v&p3" + uri"$uriAsString".params.getMulti("p1") shouldBe Some(Nil) + uri"$uriAsString".params.getMulti("p2") shouldBe Some(List("v")) + uri"$uriAsString".params.getMulti("p3") shouldBe Some(Nil) + } + + val validationTestData = List( + (() => Uri.unsafeApply("")) -> "host cannot be empty", + (() => Uri.unsafeApply("h ttp", "example.org")) -> "scheme" + ) + + for { + (createUri, expectedException) <- validationTestData + } { + test(s"""should validate and throw "$expectedException" if not valid""") { + val caught = intercept[IllegalArgumentException] { + createUri() + } + + caught.getMessage.toLowerCase() should include(expectedException) + } + } + + test("should add path ignoring the trailing empty segment if necessary") { + uri"http://x.com".addPath("a").toString shouldBe "http://x.com/a" + uri"http://x.com/".addPath("a").toString shouldBe "http://x.com/a" + uri"http://x.com/a".addPath("b").toString shouldBe "http://x.com/a/b" + uri"http://x.com".addPath("a", "b").toString shouldBe "http://x.com/a/b" + uri"http://x.com/".addPath("a", "b").toString shouldBe "http://x.com/a/b" + uri"/".addPath("a").toString shouldBe "/a" + uri"/a".addPath("b").toString shouldBe "/a/b" + uri"a".addPath("b").toString shouldBe "a/b" + uri"a/".addPath("b").toString shouldBe "a/b" + } + + test("should parse a relative uri with an absolute path") { + def pathSegment(s: String) = Uri.Segment(s, Uri.PathSegmentEncoding.Standard) + uri"/x/y".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) + uri"${"/x/y"}".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) + } +} diff --git a/core/src/test/scala/sttp/model/UriTests.scala b/core/src/test/scalajvm/sttp/model/UriTests.scala similarity index 99% rename from core/src/test/scala/sttp/model/UriTests.scala rename to core/src/test/scalajvm/sttp/model/UriTests.scala index 5f54bb05..a334954a 100644 --- a/core/src/test/scala/sttp/model/UriTests.scala +++ b/core/src/test/scalajvm/sttp/model/UriTests.scala @@ -1,12 +1,13 @@ package sttp.model -import java.net.URI -import Uri._ import org.scalatest.TryValues import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers +import sttp.model.Uri._ import sttp.model.internal.UriCompatibility +import java.net.URI + class UriTests extends AnyFunSuite with Matchers with TryValues { val HS = HostSegment From 9497cbc5391795dbd15ee4ebb635cf2400064842 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Thu, 23 May 2024 13:30:10 +0200 Subject: [PATCH 5/7] Change JVM and JS tests --- .../sttp/model/UriTests.scala | 1 - .../scalajs/sttp/model/UriTestsHelper.scala | 25 ++ .../test/scalajvm/sttp/model/UriTests.scala | 264 ------------------ .../scalajvm/sttp/model/UriTestsHelper.scala | 25 ++ 4 files changed, 50 insertions(+), 265 deletions(-) rename core/src/test/{scalajs => scala}/sttp/model/UriTests.scala (99%) create mode 100644 core/src/test/scalajs/sttp/model/UriTestsHelper.scala delete mode 100644 core/src/test/scalajvm/sttp/model/UriTests.scala create mode 100644 core/src/test/scalajvm/sttp/model/UriTestsHelper.scala diff --git a/core/src/test/scalajs/sttp/model/UriTests.scala b/core/src/test/scala/sttp/model/UriTests.scala similarity index 99% rename from core/src/test/scalajs/sttp/model/UriTests.scala rename to core/src/test/scala/sttp/model/UriTests.scala index 2dc216db..426df9f9 100644 --- a/core/src/test/scalajs/sttp/model/UriTests.scala +++ b/core/src/test/scala/sttp/model/UriTests.scala @@ -118,7 +118,6 @@ class UriTests extends AnyFunSuite with Matchers with TryValues { List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?", List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2", List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2", - List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2", List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2", List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2", List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2", diff --git a/core/src/test/scalajs/sttp/model/UriTestsHelper.scala b/core/src/test/scalajs/sttp/model/UriTestsHelper.scala new file mode 100644 index 00000000..d065b484 --- /dev/null +++ b/core/src/test/scalajs/sttp/model/UriTestsHelper.scala @@ -0,0 +1,25 @@ +package sttp.model + +import org.scalatest.TryValues +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import sttp.model.Uri._ + +class UriTestsHelper extends AnyFunSuite with Matchers with TryValues { + + val QS = QuerySegment + + val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) + + val querySegmentsTestData = List( + List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2" + ) + + for { + (segments, expected) <- querySegmentsTestData + } { + test(s"$segments should serialize to$expected") { + testUri.copy(querySegments = segments).toString should endWith(expected) + } + } +} diff --git a/core/src/test/scalajvm/sttp/model/UriTests.scala b/core/src/test/scalajvm/sttp/model/UriTests.scala deleted file mode 100644 index a334954a..00000000 --- a/core/src/test/scalajvm/sttp/model/UriTests.scala +++ /dev/null @@ -1,264 +0,0 @@ -package sttp.model - -import org.scalatest.TryValues -import org.scalatest.funsuite.AnyFunSuite -import org.scalatest.matchers.should.Matchers -import sttp.model.Uri._ -import sttp.model.internal.UriCompatibility - -import java.net.URI - -class UriTests extends AnyFunSuite with Matchers with TryValues { - - val HS = HostSegment - val PS = PathSegment - val QS = QuerySegment - - val wholeUriTestData = List( - Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) -> "http://example.com", - Uri.unsafeApply("http", None, "example.com", None, List(""), Nil, None) -> "http://example.com/", - Uri.unsafeApply( - "https", - None, - "sub.example.com", - Some(8080), - List("a", "b", "xyz"), - List(QS.KeyValue("p1", "v1"), QS.KeyValue("p2", "v2")), - Some("f") - ) -> - "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f", - Uri.unsafeApply( - "http", - None, - "example.com", - None, - List(""), - List(QS.KeyValue("p", "v"), QS.KeyValue("p", "v")), - None - ) -> "http://example.com/?p=v&p=v", - Uri.unsafeApply( - "http", - None, - "exa mple.com", - None, - List("a b", "z", "ą:ę"), - List(QS.KeyValue("p:1", "v&v"), QS.KeyValue("p2", "v v")), - None - ) -> - "http://exa%20mple.com/a%20b/z/%C4%85:%C4%99?p:1=v%26v&p2=v+v", - Uri.unsafeApply("http", Some(UserInfo("us&e/r", Some("pa ss"))), "example.com", None, Nil, Nil, None) -> - "http://us&e%2Fr:pa%20ss@example.com", - Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, Some("f:g/h i")) -> - "http://example.com#f:g/h%20i", - Uri.unsafeApply("http", None, "example.com", None, List("key=value"), Nil, None) -> - "http://example.com/key=value", - Uri.unsafeApply("2001:db8::ff00:42:8329", 8080) -> "http://[2001:db8::ff00:42:8329]:8080", - Uri.unsafeApply( - "http", - Some(Authority("example.com")), - List(Segment("a b", identity)), - Nil, - None - ) -> "http://example.com/a b", - Uri.unsafeApply("http", None, Nil, Nil, None) -> "http:", - Uri.unsafeApply("mailto", List("user@example.com")) -> "mailto:user@example.com", - Uri.unsafeApply("http", "sub..domain") -> "http://sub..domain", - Uri.relative(List("x", "y")) -> "/x/y", - Uri.relative(List("x", "y", "")) -> "/x/y/", - Uri.relative(List("")) -> "/", - Uri.relative(List("x"), Some("a")) -> "/x#a", - Uri.relative(List("x"), List(QS.KeyValue("p1", "v1")), Some("a")) -> "/x?p1=v1#a", - Uri.pathRelative(List("x", "y")) -> "x/y", - Uri.pathRelative(List("..", "x", "y")) -> "../x/y" - ) - - for { - (uri, expected) <- wholeUriTestData - } { - test(s"$uri should serialize to $expected") { - uri.toString should be(expected) - } - } - - val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) - - val pathTestData = List( - "a/b/c" -> List("a", "b", "c"), - "/a/b/c" -> List("a", "b", "c"), - "/" -> List(""), - "" -> List("") - ) - - for { - (path, expected) <- pathTestData - } { - test(s"whole path: $path, should parse as: $expected") { - testUri.withWholePath(path).path.toList should be(expected) - } - } - - val querySegmentsTestData = List( - List( - QS.KeyValue("k1", "v1"), - QS.KeyValue("k2", "v2"), - QS.KeyValue("k3", "v3"), - QS.KeyValue("k4", "v4") - ) -> "k1=v1&k2=v2&k3=v3&k4=v4", - List( - QS.KeyValue("k1", "v1"), - QS.KeyValue("k2", "v2"), - QS.Plain("-abc-"), - QS.KeyValue("k3", "v3"), - QS.KeyValue("k4", "v4") - ) -> "k1=v1&k2=v2-abc-k3=v3&k4=v4", - List(QS.KeyValue("k1", "v1"), QS.Plain("&abc&"), QS.KeyValue("k2", "v2")) -> "k1=v1%26abc%26k2=v2", - List(QS.KeyValue("k1", "v1"), QS.Plain("&abc&", encoding = QuerySegmentEncoding.Relaxed)) -> "k1=v1&abc&", - List(QS.KeyValue("k1&", "v1&", keyEncoding = QuerySegmentEncoding.Relaxed)) -> "k1&=v1%26", - List(QS.KeyValue("k1&", "v1&", valueEncoding = QuerySegmentEncoding.Relaxed)) -> "k1%26=v1&", - List(QS.Plain("ą/ę&+;?", encoding = QuerySegmentEncoding.Relaxed)) -> "%C4%85/%C4%99&+;?", - List(QS.KeyValue("k", "v1,v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%2Cv2", - List(QS.KeyValue("k", "v1-v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1-v2", - List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%7Ev2", - List(QS.KeyValue("k", "v1_v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1_v2", - List(QS.KeyValue("k", "v1.v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1.v2", - List(QS.KeyValue("k", "v1,v2")) -> "k=v1,v2", - List(QS.KeyValue("k", "+1234")) -> "k=%2B1234", - List(QS.KeyValue("k", "[]")) -> "k=%5B%5D", - List(QS.KeyValue("k", "[]", valueEncoding = QuerySegmentEncoding.RelaxedWithBrackets)) -> "k=[]" - ) - - for { - (segments, expected) <- querySegmentsTestData - } { - test(s"$segments should serialize to$expected") { - testUri.copy(querySegments = segments).toString should endWith(expected) - } - } - - private val bodyPartEncodingTestData = List( - "v1,v2" -> "v1%2Cv2", - "v1-v2" -> "v1-v2", - "v1~v2" -> "v1~v2", - "v1_v2" -> "v1_v2", - "v1.v2" -> "v1.v2", - ) - - for { - (segments, expected) <- bodyPartEncodingTestData - } { - test(s"$segments should serialize to$expected") { - UriCompatibility.encodeBodyPart(segments, "utf-8") should endWith(expected) - } - } - - val hostTestData = List( - "www.mikołak.net" -> "http://www.xn--mikoak-6db.net", - "192.168.1.0" -> "http://192.168.1.0", - "::1" -> "http://[::1]", - "2001:db8::ff00:42:8329" -> "http://[2001:db8::ff00:42:8329]", - "2001:0db8:0000:0000:0000:ff00:0042:8329" -> "http://[2001:0db8:0000:0000:0000:ff00:0042:8329]" - ) - - for { - (host, expected) <- hostTestData - } { - test(s"host $host should serialize to $expected") { - Uri.unsafeApply(host).toString should be(s"$expected") - } - } - - test("should convert from java URI") { - val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" - Uri(URI.create(uriAsString)).toString should be(uriAsString) - } - - test("should parse raw string") { - val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" - Uri.parse(uriAsString).right.map(_.toString) shouldBe Right(uriAsString) - val badString = "xyz://foobar:80:37/?&?" - Uri.parse(badString).isLeft shouldBe true - } - - test("should convert to java URI") { - val uriAsString = "https://sub.example.com:8080/a/b/xyz?p1=v1&p2=v2#f" - uri"$uriAsString".toJavaUri.toString should be(uriAsString) - } - - test("should have multi params") { - val uriAsString = "https://sub.example.com:8080?p1=v1&p2=v2&p1=v3&p2=v4" - val expectedParams = Map("p1" -> List("v1", "v3"), "p2" -> List("v2", "v4")) - uri"$uriAsString".params.toMultiMap should be(expectedParams) - } - - test("should have no multi params") { - val uriAsString = "https://sub.example.com:8080" - uri"$uriAsString".params.toMultiMap should be(Map()) - } - - test("should have non-empty multi params") { - val uriAsString = "https://sub.example.com:8080?p1&p2" - uri"$uriAsString".params.toMultiMap should be(Map("p1" -> Nil, "p2" -> Nil)) - } - - test("should have multi params with empty string values") { - val uriAsString = "https://sub.example.com:8080?p1=&p2=" - uri"$uriAsString".params.toMultiMap should be(Map("p1" -> List(""), "p2" -> List(""))) - } - - test("should have multi params with only values") { - val uriAsString = "https://sub.example.com:8080?=v1&=v2" - uri"$uriAsString".params.toMultiMap should be(Map("" -> List("v1", "v2"))) - } - - test("should replace or append path") { - uri"http://example.org/x/y".addPath("z").path shouldBe List("x", "y", "z") - uri"http://example.org/x/y".withPath("z").path shouldBe List("z") - } - - test("should replace or append query parameter") { - uri"http://example.org?x=1".addParam("y", "2").paramsMap shouldBe Map("x" -> "1", "y" -> "2") - uri"http://example.org?x=1".withParam("y", "2").paramsMap shouldBe Map("y" -> "2") - } - - test("should parse no-value parameters") { - val uriAsString = "https://sub.example.com:8080?p1&p2=v&p3" - uri"$uriAsString".params.getMulti("p1") shouldBe Some(Nil) - uri"$uriAsString".params.getMulti("p2") shouldBe Some(List("v")) - uri"$uriAsString".params.getMulti("p3") shouldBe Some(Nil) - } - - val validationTestData = List( - (() => Uri.unsafeApply("")) -> "host cannot be empty", - (() => Uri.unsafeApply("h ttp", "example.org")) -> "scheme" - ) - - for { - (createUri, expectedException) <- validationTestData - } { - test(s"""should validate and throw "$expectedException" if not valid""") { - val caught = intercept[IllegalArgumentException] { - createUri() - } - - caught.getMessage.toLowerCase() should include(expectedException) - } - } - - test("should add path ignoring the trailing empty segment if necessary") { - uri"http://x.com".addPath("a").toString shouldBe "http://x.com/a" - uri"http://x.com/".addPath("a").toString shouldBe "http://x.com/a" - uri"http://x.com/a".addPath("b").toString shouldBe "http://x.com/a/b" - uri"http://x.com".addPath("a", "b").toString shouldBe "http://x.com/a/b" - uri"http://x.com/".addPath("a", "b").toString shouldBe "http://x.com/a/b" - uri"/".addPath("a").toString shouldBe "/a" - uri"/a".addPath("b").toString shouldBe "/a/b" - uri"a".addPath("b").toString shouldBe "a/b" - uri"a/".addPath("b").toString shouldBe "a/b" - } - - test("should parse a relative uri with an absolute path") { - def pathSegment(s: String) = Uri.Segment(s, Uri.PathSegmentEncoding.Standard) - uri"/x/y".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) - uri"${"/x/y"}".pathSegments shouldBe Uri.AbsolutePath(List(pathSegment("x"), pathSegment("y"))) - } -} diff --git a/core/src/test/scalajvm/sttp/model/UriTestsHelper.scala b/core/src/test/scalajvm/sttp/model/UriTestsHelper.scala new file mode 100644 index 00000000..10752c97 --- /dev/null +++ b/core/src/test/scalajvm/sttp/model/UriTestsHelper.scala @@ -0,0 +1,25 @@ +package sttp.model + +import org.scalatest.TryValues +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import sttp.model.Uri._ + +class UriTestsHelper extends AnyFunSuite with Matchers with TryValues { + + val QS = QuerySegment + + val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) + + val querySegmentsTestData = List( + List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%7Ev2" + ) + + for { + (segments, expected) <- querySegmentsTestData + } { + test(s"$segments should serialize to$expected") { + testUri.copy(querySegments = segments).toString should endWith(expected) + } + } +} From 3eca264c9ee722643586c96ad0de8da9759a6c91 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Fri, 24 May 2024 08:36:47 +0200 Subject: [PATCH 6/7] Change for to while --- core/src/main/scala/sttp/model/internal/Rfc3986.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/scala/sttp/model/internal/Rfc3986.scala b/core/src/main/scala/sttp/model/internal/Rfc3986.scala index 52f490ba..5cb08a6f 100644 --- a/core/src/main/scala/sttp/model/internal/Rfc3986.scala +++ b/core/src/main/scala/sttp/model/internal/Rfc3986.scala @@ -52,7 +52,10 @@ object Rfc3986 { ): String = { val sb = new StringBuilder() // based on https://gist.github.com/teigen/5865923 - for (b <- s.getBytes(enc)) { + val bytes: Array[Byte] = s.getBytes(enc) + var i = 0 + while (i < bytes.length) { + val b: Byte = bytes(i) val c = (b & 0xff).toChar if (c == '+' && encodePlus) sb.append("%2B") // #48 else if (allowedCharacters(c)) sb.append(c) @@ -61,6 +64,7 @@ object Rfc3986 { sb.append("%") sb.append(Rfc3986Compatibility.formatByte(b)) } + i += 1 } sb.toString } From 51b87b5f939b141256fa87d7647c7487151812d8 Mon Sep 17 00:00:00 2001 From: "bartlomiej.zylinski" Date: Mon, 27 May 2024 08:54:47 +0200 Subject: [PATCH 7/7] Change way of handling platform specific tests --- core/src/test/scala/sttp/model/UriTests.scala | 2 +- .../{UriTestsHelper.scala => UriTestsExtension.scala} | 10 +++------- .../{UriTestsHelper.scala => UriTestsExtension.scala} | 10 +++------- .../scalanative/sttp/model/UriTestsExtension.scala | 8 ++++++++ 4 files changed, 15 insertions(+), 15 deletions(-) rename core/src/test/scalajs/sttp/model/{UriTestsHelper.scala => UriTestsExtension.scala} (61%) rename core/src/test/scalajvm/sttp/model/{UriTestsHelper.scala => UriTestsExtension.scala} (61%) create mode 100644 core/src/test/scalanative/sttp/model/UriTestsExtension.scala diff --git a/core/src/test/scala/sttp/model/UriTests.scala b/core/src/test/scala/sttp/model/UriTests.scala index 426df9f9..24bc91f7 100644 --- a/core/src/test/scala/sttp/model/UriTests.scala +++ b/core/src/test/scala/sttp/model/UriTests.scala @@ -8,7 +8,7 @@ import sttp.model.internal.UriCompatibility import java.net.URI -class UriTests extends AnyFunSuite with Matchers with TryValues { +class UriTests extends AnyFunSuite with Matchers with TryValues with UriTestsExtension { val HS = HostSegment val PS = PathSegment diff --git a/core/src/test/scalajs/sttp/model/UriTestsHelper.scala b/core/src/test/scalajs/sttp/model/UriTestsExtension.scala similarity index 61% rename from core/src/test/scalajs/sttp/model/UriTestsHelper.scala rename to core/src/test/scalajs/sttp/model/UriTestsExtension.scala index d065b484..7d630ad3 100644 --- a/core/src/test/scalajs/sttp/model/UriTestsHelper.scala +++ b/core/src/test/scalajs/sttp/model/UriTestsExtension.scala @@ -5,18 +5,14 @@ import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import sttp.model.Uri._ -class UriTestsHelper extends AnyFunSuite with Matchers with TryValues { +trait UriTestsExtension extends AnyFunSuite with Matchers with TryValues { this: UriTests => - val QS = QuerySegment - - val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) - - val querySegmentsTestData = List( + private val tildeEncodingTest = List( List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1~v2" ) for { - (segments, expected) <- querySegmentsTestData + (segments, expected) <- tildeEncodingTest } { test(s"$segments should serialize to$expected") { testUri.copy(querySegments = segments).toString should endWith(expected) diff --git a/core/src/test/scalajvm/sttp/model/UriTestsHelper.scala b/core/src/test/scalajvm/sttp/model/UriTestsExtension.scala similarity index 61% rename from core/src/test/scalajvm/sttp/model/UriTestsHelper.scala rename to core/src/test/scalajvm/sttp/model/UriTestsExtension.scala index 10752c97..ef5e5685 100644 --- a/core/src/test/scalajvm/sttp/model/UriTestsHelper.scala +++ b/core/src/test/scalajvm/sttp/model/UriTestsExtension.scala @@ -5,18 +5,14 @@ import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers import sttp.model.Uri._ -class UriTestsHelper extends AnyFunSuite with Matchers with TryValues { +trait UriTestsExtension extends AnyFunSuite with Matchers with TryValues { this: UriTests => - val QS = QuerySegment - - val testUri = Uri.unsafeApply("http", None, "example.com", None, Nil, Nil, None) - - val querySegmentsTestData = List( + private val tildeEncodingTest = List( List(QS.KeyValue("k", "v1~v2", valueEncoding = QuerySegmentEncoding.All)) -> "k=v1%7Ev2" ) for { - (segments, expected) <- querySegmentsTestData + (segments, expected) <- tildeEncodingTest } { test(s"$segments should serialize to$expected") { testUri.copy(querySegments = segments).toString should endWith(expected) diff --git a/core/src/test/scalanative/sttp/model/UriTestsExtension.scala b/core/src/test/scalanative/sttp/model/UriTestsExtension.scala new file mode 100644 index 00000000..5b8c6090 --- /dev/null +++ b/core/src/test/scalanative/sttp/model/UriTestsExtension.scala @@ -0,0 +1,8 @@ +package sttp.model + +import org.scalatest.TryValues +import org.scalatest.funsuite.AnyFunSuite +import org.scalatest.matchers.should.Matchers +import sttp.model.Uri._ + +trait UriTestsExtension \ No newline at end of file