From b09f428cb0c80553f971f2cf4eef80f105cfba20 Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Fri, 8 Oct 2021 11:27:32 +0200 Subject: [PATCH 1/2] Added static content test to finatra --- .../server/finatra/FinatraServerTest.scala | 4 +- .../tests/ServerStaticContentTests.scala | 523 +++++++++--------- 2 files changed, 267 insertions(+), 260 deletions(-) diff --git a/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala b/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala index 1da5188777..a93e6f1bde 100644 --- a/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala +++ b/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala @@ -8,6 +8,7 @@ import sttp.tapir.server.tests.{ ServerBasicTests, ServerFileMultipartTests, ServerMetricsTest, + ServerStaticContentTests, backendResource } import sttp.tapir.tests.{Test, TestSuite} @@ -21,6 +22,7 @@ class FinatraServerTest extends TestSuite { new ServerBasicTests(createServerTest, interpreter).tests() ++ new ServerFileMultipartTests(createServerTest).tests() ++ new ServerAuthenticationTests(createServerTest).tests() ++ - new ServerMetricsTest(createServerTest).tests() + new ServerMetricsTest(createServerTest).tests() ++ + new ServerStaticContentTests(interpreter, backend, false).tests() } } diff --git a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerStaticContentTests.scala b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerStaticContentTests.scala index e437927ef2..66135db7b0 100644 --- a/server/tests/src/main/scala/sttp/tapir/server/tests/ServerStaticContentTests.scala +++ b/server/tests/src/main/scala/sttp/tapir/server/tests/ServerStaticContentTests.scala @@ -19,176 +19,296 @@ import scala.concurrent.Future class ServerStaticContentTests[F[_], ROUTE]( serverInterpreter: TestServerInterpreter[F, Any, ROUTE], - backend: SttpBackend[IO, Fs2Streams[IO] with WebSockets] + backend: SttpBackend[IO, Fs2Streams[IO] with WebSockets], + supportSettingContentLength: Boolean = true ) { - def tests(): List[Test] = List( - Test("serve files from the given system path") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) - .use { port => - def get(path: List[String]) = basicRequest - .get(uri"http://localhost:$port/$path") - .response(asStringAlways) - .send(backend) + def tests(): List[Test] = { + val baseTests = List( + Test("serve files from the given system path") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) + .use { port => + def get(path: List[String]) = basicRequest + .get(uri"http://localhost:$port/$path") + .response(asStringAlways) + .send(backend) - get("f1" :: Nil).map(_.body shouldBe "f1 content") >> - get("f2" :: Nil).map(_.body shouldBe "f2 content") >> - get("d1" :: "f3" :: Nil).map(_.body shouldBe "f3 content") >> - get("d1" :: "d2" :: "f4" :: Nil).map(_.body shouldBe "f4 content") - } - .unsafeToFuture() - } - }, - Test("serve files from the given system path with a prefix") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("static" / "content")(testDir.getAbsolutePath)) - .use { port => - def get(path: List[String]) = basicRequest - .get(uri"http://localhost:$port/$path") - .response(asStringAlways) - .send(backend) + get("f1" :: Nil).map(_.body shouldBe "f1 content") >> + get("f2" :: Nil).map(_.body shouldBe "f2 content") >> + get("d1" :: "f3" :: Nil).map(_.body shouldBe "f3 content") >> + get("d1" :: "d2" :: "f4" :: Nil).map(_.body shouldBe "f4 content") + } + .unsafeToFuture() + } + }, + Test("serve files from the given system path with a prefix") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("static" / "content")(testDir.getAbsolutePath)) + .use { port => + def get(path: List[String]) = basicRequest + .get(uri"http://localhost:$port/$path") + .response(asStringAlways) + .send(backend) - get("static" :: "content" :: "f1" :: Nil).map(_.body shouldBe "f1 content") >> - get("static" :: "content" :: "d1" :: "f3" :: Nil).map(_.body shouldBe "f3 content") - } - .unsafeToFuture() - } - }, - Test("serve index.html when a directory is requested") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) - .use { port => - basicRequest - .get(uri"http://localhost:$port/d1") - .response(asStringAlways) - .send(backend) - .map(_.body shouldBe "index content") - } - .unsafeToFuture() - } - }, - Test("return 404 when files are not found") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) - .use { port => - basicRequest - .headers(Header(HeaderNames.Range, "bytes=0-1")) - .get(uri"http://localhost:$port/test") - .response(asStringAlways) - .send(backend) - .map(_.headers contains Header(HeaderNames.AcceptRanges, "bytes") shouldBe true) - } - .unsafeToFuture() - } - }, - Test("should return whole while file if header not present ") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) - .use { port => - basicRequest - .get(uri"http://localhost:$port/test") - .response(asStringAlways) - .send(backend) - .map(_.body shouldBe "f1 content") + get("static" :: "content" :: "f1" :: Nil).map(_.body shouldBe "f1 content") >> + get("static" :: "content" :: "d1" :: "f3" :: Nil).map(_.body shouldBe "f3 content") + } + .unsafeToFuture() + } + }, + Test("serve index.html when a directory is requested") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) + .use { port => + basicRequest + .get(uri"http://localhost:$port/d1") + .response(asStringAlways) + .send(backend) + .map(_.body shouldBe "index content") + } + .unsafeToFuture() + } + }, + Test("return 404 when files are not found") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=0-1")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.headers contains Header(HeaderNames.AcceptRanges, "bytes") shouldBe true) + } + .unsafeToFuture() + } + }, + Test("should return whole while file if header not present ") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.body shouldBe "f1 content") - } - .unsafeToFuture() - } - }, - Test("returns 200 status code for whole file") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) - .use { port => - basicRequest - .get(uri"http://localhost:$port/test") - .response(asStringAlways) - .send(backend) - .map(_.code shouldBe StatusCode.Ok) + } + .unsafeToFuture() + } + }, + Test("returns 200 status code for whole file") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.code shouldBe StatusCode.Ok) - } - .unsafeToFuture() - } - }, - Test("should return 416 if over range") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) - .use { port => - basicRequest - .headers(Header(HeaderNames.Range, "bytes=0-11")) - .get(uri"http://localhost:$port/test") - .response(asStringAlways) - .send(backend) - .map(_.code shouldBe StatusCode.RangeNotSatisfiable) - } - .unsafeToFuture() - } - }, - Test("returns content range header with matching bytes") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + } + .unsafeToFuture() + } + }, + Test("should return 416 if over range") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=0-11")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.code shouldBe StatusCode.RangeNotSatisfiable) + } + .unsafeToFuture() + } + }, + Test("returns content range header with matching bytes") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=1-3")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.headers contains Header(HeaderNames.ContentRange, "bytes 1-3/10") shouldBe true) + } + .unsafeToFuture() + } + }, + Test("returns 206 status code for partial content") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=1-3")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.code shouldBe StatusCode.PartialContent) + } + .unsafeToFuture() + } + }, + Test("should return bytes 4-7 from file") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=4-7")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.body shouldBe "onte") + } + .unsafeToFuture() + } + }, + Test("should return bytes 100000-200000 from file") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f5").toFile.getAbsolutePath)) + .use { port => + basicRequest + .headers(Header(HeaderNames.Range, "bytes=100000-200000")) + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map { r => + r.body.length shouldBe 100001 + r.body.head shouldBe 'x' + } + } + .unsafeToFuture() + } + }, + Test("if an etag is present, only return the file if it doesn't match the etag") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) + .use { port => + def get(etag: Option[String]) = basicRequest + .get(uri"http://localhost:$port/f1") + .header(HeaderNames.IfNoneMatch, etag) + .response(asStringAlways) + .send(backend) + + get(None).flatMap { r1 => + r1.code shouldBe StatusCode.Ok + val etag = r1.header(HeaderNames.Etag).get + + get(Some(etag)).map { r2 => + r2.code shouldBe StatusCode.NotModified + } >> get(Some(etag.replace("-", "-x"))).map { r2 => + r2.code shouldBe StatusCode.Ok + } + } + } + .unsafeToFuture() + } + }, + Test("serve a single resource") { + serveRoute(resourceServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test/r1.txt")) .use { port => basicRequest - .headers(Header(HeaderNames.Range, "bytes=1-3")) - .get(uri"http://localhost:$port/test") + .get(uri"http://localhost:$port/$path") .response(asStringAlways) .send(backend) - .map(_.headers contains Header(HeaderNames.ContentRange, "bytes 1-3/10") shouldBe true) + .map(_.body shouldBe "Resource 1") } .unsafeToFuture() - } - }, - Test("returns 206 status code for partial content") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + }, + Test("not return a resource outside of the resource prefix directory") { + serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) .use { port => basicRequest - .headers(Header(HeaderNames.Range, "bytes=1-3")) - .get(uri"http://localhost:$port/test") + .get(uri"http://localhost:$port/../test2/r5.txt") .response(asStringAlways) .send(backend) - .map(_.code shouldBe StatusCode.PartialContent) + .map(_.body should not be "Resource 5") } .unsafeToFuture() - } - }, - Test("should return bytes 4-7 from file") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + }, + Test("return file metadata") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) + .use { port => + basicRequest + .get(uri"http://localhost:$port/img.gif") + .response(asStringAlways) + .send(backend) + .map { r => + r.contentLength shouldBe Some(11) + r.contentType shouldBe Some(MediaType.ImageGif.toString()) + r.header(HeaderNames.LastModified) + .flatMap(t => Header.parseHttpDate(t).toOption) + .map(_.toEpochMilli) + .get should be > (System.currentTimeMillis() - 10000L) + r.header(HeaderNames.Etag).isDefined shouldBe true + } + } + .unsafeToFuture() + } + }, + Test("serve resources") { + serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) .use { port => - basicRequest - .headers(Header(HeaderNames.Range, "bytes=4-7")) - .get(uri"http://localhost:$port/test") + def get(path: List[String]) = basicRequest + .get(uri"http://localhost:$port/$path") .response(asStringAlways) .send(backend) - .map(_.body shouldBe "onte") + + get("r1.txt" :: Nil).map(_.body shouldBe "Resource 1") >> + get("r2.txt" :: Nil).map(_.body shouldBe "Resource 2") >> + get("d1/r3.txt" :: Nil).map(_.body shouldBe "Resource 3") >> + get("d1/d2/r4.txt" :: Nil).map(_.body shouldBe "Resource 4") } .unsafeToFuture() - } - }, - Test("should return bytes 100000-200000 from file") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F]("test")(testDir.toPath.resolve("f5").toFile.getAbsolutePath)) + }, + Test("not return a file outside of the system path") { + withTestFilesDirectory { testDir => + serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath + "/d1")) + .use { port => + basicRequest + .get(uri"http://localhost:$port/../f1") + .response(asStringAlways) + .send(backend) + .map(_.body should not be "f1 content") + } + .unsafeToFuture() + } + }, + Test("return 404 when a resource is not found") { + serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) .use { port => basicRequest - .headers(Header(HeaderNames.Range, "bytes=100000-200000")) - .get(uri"http://localhost:$port/test") + .get(uri"http://localhost:$port/r3") .response(asStringAlways) .send(backend) - .map { r => - r.body.length shouldBe 100001 - r.body.head shouldBe 'x' - } + .map(_.code shouldBe StatusCode.NotFound) } .unsafeToFuture() - } - }, - Test("if an etag is present, only return the file if it doesn't match the etag") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) + }, + Test("serve a single file from the given system path") { + withTestFilesDirectory { testDir => + serveRoute(fileServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) + .use { port => + basicRequest + .get(uri"http://localhost:$port/test") + .response(asStringAlways) + .send(backend) + .map(_.body shouldBe "f1 content") + } + .unsafeToFuture() + } + }, + Test("if an etag is present, only return the resource if it doesn't match the etag") { + serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) .use { port => def get(etag: Option[String]) = basicRequest - .get(uri"http://localhost:$port/f1") + .get(uri"http://localhost:$port/r1.txt") .header(HeaderNames.IfNoneMatch, etag) .response(asStringAlways) .send(backend) @@ -206,51 +326,8 @@ class ServerStaticContentTests[F[_], ROUTE]( } .unsafeToFuture() } - }, - Test("return file metadata") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath)) - .use { port => - basicRequest - .get(uri"http://localhost:$port/img.gif") - .response(asStringAlways) - .send(backend) - .map { r => - r.contentLength shouldBe Some(11) - r.contentType shouldBe Some(MediaType.ImageGif.toString()) - r.header(HeaderNames.LastModified) - .flatMap(t => Header.parseHttpDate(t).toOption) - .map(_.toEpochMilli) - .get should be > (System.currentTimeMillis() - 10000L) - r.header(HeaderNames.Etag).isDefined shouldBe true - } - } - .unsafeToFuture() - } - }, - Test("serve a single resource") { - serveRoute(resourceServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test/r1.txt")) - .use { port => - basicRequest - .get(uri"http://localhost:$port/$path") - .response(asStringAlways) - .send(backend) - .map(_.body shouldBe "Resource 1") - } - .unsafeToFuture() - }, - Test("not return a resource outside of the resource prefix directory") { - serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) - .use { port => - basicRequest - .get(uri"http://localhost:$port/../test2/r5.txt") - .response(asStringAlways) - .send(backend) - .map(_.body should not be "Resource 5") - } - .unsafeToFuture() - }, - Test("return resource metadata") { + ) + val resourceMetadataTest = Test("return resource metadata") { serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) .use { port => basicRequest @@ -268,82 +345,10 @@ class ServerStaticContentTests[F[_], ROUTE]( } } .unsafeToFuture() - }, - Test("serve resources") { - serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) - .use { port => - def get(path: List[String]) = basicRequest - .get(uri"http://localhost:$port/$path") - .response(asStringAlways) - .send(backend) - - get("r1.txt" :: Nil).map(_.body shouldBe "Resource 1") >> - get("r2.txt" :: Nil).map(_.body shouldBe "Resource 2") >> - get("d1/r3.txt" :: Nil).map(_.body shouldBe "Resource 3") >> - get("d1/d2/r4.txt" :: Nil).map(_.body shouldBe "Resource 4") - } - .unsafeToFuture() - }, - Test("not return a file outside of the system path") { - withTestFilesDirectory { testDir => - serveRoute(filesServerEndpoint[F](emptyInput)(testDir.getAbsolutePath + "/d1")) - .use { port => - basicRequest - .get(uri"http://localhost:$port/../f1") - .response(asStringAlways) - .send(backend) - .map(_.body should not be "f1 content") - } - .unsafeToFuture() - } - }, - Test("return 404 when a resource is not found") { - serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) - .use { port => - basicRequest - .get(uri"http://localhost:$port/r3") - .response(asStringAlways) - .send(backend) - .map(_.code shouldBe StatusCode.NotFound) - } - .unsafeToFuture() - }, - Test("serve a single file from the given system path") { - withTestFilesDirectory { testDir => - serveRoute(fileServerEndpoint[F]("test")(testDir.toPath.resolve("f1").toFile.getAbsolutePath)) - .use { port => - basicRequest - .get(uri"http://localhost:$port/test") - .response(asStringAlways) - .send(backend) - .map(_.body shouldBe "f1 content") - } - .unsafeToFuture() - } - }, - Test("if an etag is present, only return the resource if it doesn't match the etag") { - serveRoute(resourcesServerEndpoint[F](emptyInput)(classOf[ServerStaticContentTests[F, ROUTE]].getClassLoader, "test")) - .use { port => - def get(etag: Option[String]) = basicRequest - .get(uri"http://localhost:$port/r1.txt") - .header(HeaderNames.IfNoneMatch, etag) - .response(asStringAlways) - .send(backend) - - get(None).flatMap { r1 => - r1.code shouldBe StatusCode.Ok - val etag = r1.header(HeaderNames.Etag).get - - get(Some(etag)).map { r2 => - r2.code shouldBe StatusCode.NotModified - } >> get(Some(etag.replace("-", "-x"))).map { r2 => - r2.code shouldBe StatusCode.Ok - } - } - } - .unsafeToFuture() } - ) + if (supportSettingContentLength) baseTests :+ resourceMetadataTest + else baseTests + } def serveRoute[I, E, O](e: ServerEndpoint[I, E, O, Any, F]): Resource[IO, Port] = serverInterpreter.server(NonEmptyList.of(serverInterpreter.route(e))) From 0bc687094c645a6246472236a7c619f7fcca2c19 Mon Sep 17 00:00:00 2001 From: bartekzylinski Date: Fri, 8 Oct 2021 11:51:34 +0200 Subject: [PATCH 2/2] Added name to boolean paramter --- .../scala/sttp/tapir/server/finatra/FinatraServerTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala b/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala index a93e6f1bde..a9113db01d 100644 --- a/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala +++ b/server/finatra-server/src/test/scala/sttp/tapir/server/finatra/FinatraServerTest.scala @@ -23,6 +23,6 @@ class FinatraServerTest extends TestSuite { new ServerFileMultipartTests(createServerTest).tests() ++ new ServerAuthenticationTests(createServerTest).tests() ++ new ServerMetricsTest(createServerTest).tests() ++ - new ServerStaticContentTests(interpreter, backend, false).tests() + new ServerStaticContentTests(interpreter, backend, supportSettingContentLength = false).tests() } }