From fcae020fb22376ad406931d95850f0d6eb6845f8 Mon Sep 17 00:00:00 2001 From: datalek Date: Mon, 28 Nov 2016 08:33:20 +0100 Subject: [PATCH] Add implementation for RequestBuilder and Response --- project/Build.scala | 9 +- silhouette-akka-http-client/build.sbt | 24 ++ .../akka/http/client/AkkaHttpClient.scala | 28 ++ .../http/client/AkkaHttpRequestBuilder.scala | 80 ++++++ .../akka/http/client/AkkaHttpResponse.scala | 37 +++ .../client/AkkaHttpRequestBuilderSpec.scala | 263 ++++++++++++++++++ .../http/client/AkkaHttpResponseSpec.scala | 209 ++++++++++++++ .../akka/http/session/Session.scala | 2 +- 8 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 silhouette-akka-http-client/build.sbt create mode 100644 silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpClient.scala create mode 100644 silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpRequestBuilder.scala create mode 100644 silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpResponse.scala create mode 100644 silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpRequestBuilderSpec.scala create mode 100644 silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpResponseSpec.scala diff --git a/project/Build.scala b/project/Build.scala index 4257cdc..dc84bbc 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -26,11 +26,18 @@ object Build extends Build { base = file("silhouette-akka-http") ) + val silhouetteAkkaHttpClient = Project( + id = "silhouette-akka-http-client", + base = file("silhouette-akka-http-client"), + dependencies = Seq(silhouetteAkkaHttp) + ) + val root = Project( id = "root", base = file("."), aggregate = Seq( - silhouetteAkkaHttp + silhouetteAkkaHttp, + silhouetteAkkaHttpClient ), settings = Defaults.coreDefaultSettings ++ APIDoc.settings ++ diff --git a/silhouette-akka-http-client/build.sbt b/silhouette-akka-http-client/build.sbt new file mode 100644 index 0000000..85bdb72 --- /dev/null +++ b/silhouette-akka-http-client/build.sbt @@ -0,0 +1,24 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Dependencies._ + +libraryDependencies ++= Seq( + Library.Specs2.core % "test" +) + +enablePlugins(Doc) diff --git a/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpClient.scala b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpClient.scala new file mode 100644 index 0000000..b12082c --- /dev/null +++ b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpClient.scala @@ -0,0 +1,28 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package silhouette.akka.http.client + +import akka.actor.ActorSystem +import akka.stream.Materializer +import silhouette.http.client.HttpClient + +class AkkaHttpClient()(implicit system: ActorSystem, fm: Materializer) extends HttpClient[AkkaHttpRequestBuilder] { + + override def requestBuilder: AkkaHttpRequestBuilder = AkkaHttpRequestBuilder() + +} diff --git a/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpRequestBuilder.scala b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpRequestBuilder.scala new file mode 100644 index 0000000..064a8ec --- /dev/null +++ b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpRequestBuilder.scala @@ -0,0 +1,80 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package silhouette.akka.http.client + +import akka.actor.ActorSystem +import akka.http.scaladsl.Http +import akka.http.scaladsl.model._ +import akka.stream.Materializer +import silhouette.akka.http.AkkaHttpRequestPipeline +import silhouette.http.client.{ Body, RequestBuilder, Response, ContentType => SContentType } +import scala.concurrent.duration._ +import scala.concurrent.{ ExecutionContext, Future } +import scala.io.Codec + +/** + * The request implementation based on the `akka.http.scaladsl.model.HttpRequest`. + * + * @param request The request this pipeline handles. + */ +case class AkkaHttpRequestBuilder( + request: HttpRequest = HttpRequest() +)(implicit system: ActorSystem, fm: Materializer) extends RequestBuilder { + + override type Self = AkkaHttpRequestBuilder + + implicit val ec: ExecutionContext = system.dispatcher + + private val akkaHttpRequestPipeline = AkkaHttpRequestPipeline(request, sessionName = "session") + + override def withUrl(url: String): Self = { + copy(request = akkaHttpRequestPipeline.request.withUri(url)) + } + + override def withMethod(method: String): Self = { + copy(request = akkaHttpRequestPipeline.request.withMethod(HttpMethods.getForKey(method).get)) + } + + override def withHeaders(headers: (String, String)*): Self = { + copy(request = akkaHttpRequestPipeline.withHeaders(headers: _*).request) + } + + override def withQueryParams(params: (String, String)*): Self = { + copy(request = akkaHttpRequestPipeline.withQueryParams(params: _*).request) + } + + override def withBody(body: Body): Self = { + import scala.collection.JavaConverters._ + val mediaType = MediaType.parse(body.contentType.value).getOrElse(MediaTypes.`application/json`) + val charset = HttpCharset(body.codec.charSet.name())(body.codec.charSet.aliases().asScala.toList) + val contentType = ContentType(mediaType, () => charset) + val entity = HttpEntity(contentType, body.data) + copy(request = akkaHttpRequestPipeline.request.withEntity(entity)) + } + + override def execute: Future[Response] = { + Http().singleRequest(request).flatMap { response => + // TODO: get timeout from configuration + response.entity.toStrict(10.seconds).map { entity => + val contentType = SContentType(entity.contentType.value) + val codec = entity.contentType.charsetOption.map(c => Codec(c.value)).getOrElse(Body.DefaultCodec) + AkkaHttpResponse(response, Body(contentType, codec, entity.data.toArray)) + } + } + } +} diff --git a/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpResponse.scala b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpResponse.scala new file mode 100644 index 0000000..cc57afe --- /dev/null +++ b/silhouette-akka-http-client/src/main/scala/silhouette/akka/http/client/AkkaHttpResponse.scala @@ -0,0 +1,37 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package silhouette.akka.http.client + +import akka.http.scaladsl.model.HttpResponse +import silhouette.akka.http.AkkaHttpResponsePipeline +import silhouette.http.client.{ Body, Response } + +/** + * The response implementation based on the [[akka.http.scaladsl.model.HttpResponse]]. + * + * @param response The response this pipeline handles. + */ +case class AkkaHttpResponse(response: HttpResponse, body: Body) extends Response { + + val akkaHttpResponsePipeline = AkkaHttpResponsePipeline(response, sessionName = "session") + + override def header: Map[String, Seq[String]] = akkaHttpResponsePipeline.headers + + override def status: Int = akkaHttpResponsePipeline.response.status.intValue() + +} diff --git a/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpRequestBuilderSpec.scala b/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpRequestBuilderSpec.scala new file mode 100644 index 0000000..a468878 --- /dev/null +++ b/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpRequestBuilderSpec.scala @@ -0,0 +1,263 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package silhouette.akka.http.client + +import akka.http.scaladsl.model.Uri.Query +import akka.http.scaladsl.model.headers.RawHeader +import akka.http.scaladsl.model.{ HttpRequest, Uri } +import org.specs2.mutable.Specification +import org.specs2.specification.Scope +import silhouette.akka.http.AkkaHttpRequestPipeline +import silhouette.akka.http.session.Session +import silhouette.http.Cookie + +/** + * Test case for the [[AkkaHttpRequestBuilder]] class. + */ +class AkkaHttpRequestBuilderSpec extends Specification { + + "The `headers` method" should { + "return all headers" in new Context { + requestPipeline.headers.map(_._2.sorted) must be equalTo headers.map(_._2.sorted) + } + } + + "The `header` method" should { + "return the list of header values" in new Context { + requestPipeline.header("TEST1") must contain(exactly("value1", "value2")) + } + + "return an empty list if no header with the given name was found" in new Context { + requestPipeline.header("TEST3") must beEmpty + } + } + + "The `withHeaders` method" should { + "append a new header" in new Context { + val r = requestPipeline.withHeaders("TEST3" -> "value1") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1")) + } + + "append multiple headers" in new Context { + val r = requestPipeline.withHeaders("TEST3" -> "value1", "TEST4" -> "value1") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1")) + r.header("TEST4") must contain(exactly("value1")) + } + + "append multiple headers with the same name" in new Context { + val r = requestPipeline.withHeaders("TEST3" -> "value1", "TEST3" -> "value2") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1", "value2")) + } + + "override an existing header" in new Context { + val r = requestPipeline.withHeaders("TEST2" -> "value2", "TEST2" -> "value3") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value2", "value3")) + } + + "override multiple existing headers" in new Context { + val r = requestPipeline.withHeaders("TEST1" -> "value3", "TEST2" -> "value2") + r.header("TEST1") must contain(exactly("value3")) + r.header("TEST2") must contain(exactly("value2")) + } + } + + "The `cookies` method" should { + "return all cookies" in new Context { + requestPipeline.cookies must be equalTo cookies + } + } + + "The `cookie` method" should { + "return some cookie for the given name" in new Context { + requestPipeline.cookie("test1") must beSome(Cookie("test1", "value1")) + } + + "return None if no cookie with the given name was found" in new Context { + requestPipeline.cookie("test3") must beNone + } + } + + "The `withCookies` method" should { + "append a new cookie" in new Context { + val r = requestPipeline.withCookies(Cookie("test3", "value3")) + r.cookie("test1") must beSome(Cookie("test1", "value1")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + r.cookie("test3") must beSome(Cookie("test3", "value3")) + } + + "override an existing cookie" in new Context { + val r = requestPipeline.withCookies(Cookie("test1", "value3")) + r.cookie("test1") must beSome(Cookie("test1", "value3")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + } + + "use the last cookie if multiple cookies with the same name are given" in new Context { + val r = requestPipeline.withCookies(Cookie("test1", "value3"), Cookie("test1", "value4")) + r.cookie("test1") must beSome(Cookie("test1", "value4")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + } + } + + "The `session` method" should { + "return all session data" in new Context { + requestPipeline.session must be equalTo session + } + } + + "The `withSession` method" should { + "append new session data" in new Context { + requestPipeline.withSession("test3" -> "value3").session must be equalTo Map( + "test1" -> "value1", + "test2" -> "value2", + "test3" -> "value3" + ) + } + + "override existing session data" in new Context { + requestPipeline.withSession("test1" -> "value3").session must be equalTo Map( + "test1" -> "value3", + "test2" -> "value2" + ) + } + + "use the last session data if multiple session data with the same name are given" in new Context { + requestPipeline.withSession("test1" -> "value3", "test1" -> "value4").session must be equalTo Map( + "test1" -> "value4", + "test2" -> "value2" + ) + } + } + + "The `rawQueryString` method" should { + "return the raw query string" in new Context { + requestPipeline.rawQueryString must be equalTo "test1=value1&test1=value2&test2=value1" + } + + "be URL encoded" in new Context { + requestPipeline.withQueryParams("test=3" -> "value=4").rawQueryString must be equalTo + "test1=value1&test1=value2&test2=value1&test%3D3=value%3D4" + } + } + + "The `queryParams` method" should { + "return all query params" in new Context { + requestPipeline.queryParams.map(_._2.sorted) must be equalTo queryParams.map(_._2.sorted) + } + } + + "The `queryParam` method" should { + "return the list of query params" in new Context { + requestPipeline.queryParam("test1") must contain(exactly("value1", "value2")) + } + + "return an empty list if no query param with the given name was found" in new Context { + requestPipeline.queryParam("test3") must beEmpty + } + } + + "The `withQueryParams` method" should { + "append a new header" in new Context { + val r = requestPipeline.withQueryParams("test3" -> "value1") + r.queryParam("test1") must contain(exactly("value1", "value2")) + r.queryParam("test2") must contain(exactly("value1")) + r.queryParam("test3") must contain(exactly("value1")) + } + + "append multiple headers" in new Context { + val r = requestPipeline.withQueryParams("test3" -> "value1", "test4" -> "value1") + r.queryParam("test1") must contain(exactly("value1", "value2")) + r.queryParam("test2") must contain(exactly("value1")) + r.queryParam("test3") must contain(exactly("value1")) + r.queryParam("test4") must contain(exactly("value1")) + } + + "append multiple headers with the same name" in new Context { + val r = requestPipeline.withQueryParams("test3" -> "value1", "test3" -> "value2") + r.queryParam("test1") must contain(exactly("value1", "value2")) + r.queryParam("test2") must contain(exactly("value1")) + r.queryParam("test3") must contain(exactly("value1", "value2")) + } + + "override an existing header" in new Context { + val r = requestPipeline.withQueryParams("test2" -> "value2", "test2" -> "value3") + r.queryParam("test1") must contain(exactly("value1", "value2")) + r.queryParam("test2") must contain(exactly("value2", "value3")) + } + + "override multiple existing headers" in new Context { + val r = requestPipeline.withQueryParams("test1" -> "value3", "test2" -> "value2") + r.queryParam("test1") must contain(exactly("value3")) + r.queryParam("test2") must contain(exactly("value2")) + } + } + + "The `unbox` method" should { + "return the handled request" in new Context { + requestPipeline.unbox must be equalTo request + } + } + + /** + * The context. + */ + trait Context extends Scope { + + val sessionName = "session" + + val headers = Map( + "TEST1" -> Seq("value1", "value2"), + "TEST2" -> Seq("value1") + ) + val session = Map( + "test1" -> "value1", + "test2" -> "value2" + ) + val cookies = Seq( + Cookie("test1", "value1"), + Cookie("test2", "value2"), + Session.asCookie(Session(sessionName, session)) + ) + val queryParams = Map( + "test1" -> Seq("value1", "value2"), + "test2" -> Seq("value1") + ) + + val akkaHeaders = headers.flatMap(p => p._2.map(v => RawHeader(p._1, v))).toList + val akkaCookie = cookies.map(c => akka.http.scaladsl.model.headers.`Cookie`(c.name, c.value)) + val akkaQueryParams = Query(queryParams.map { + case (name, values) => values.map(v => name -> v) + }.flatten.toList: _*) + val request = HttpRequest( + headers = akkaHeaders ++ akkaCookie, + uri = Uri().withQuery(akkaQueryParams) + ) + + /** + * A request pipeline which handles a request. + */ + val requestPipeline = AkkaHttpRequestPipeline(request, sessionName) + } +} + diff --git a/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpResponseSpec.scala b/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpResponseSpec.scala new file mode 100644 index 0000000..cc82434 --- /dev/null +++ b/silhouette-akka-http-client/src/test/scala/silhouette/akka/http/client/AkkaHttpResponseSpec.scala @@ -0,0 +1,209 @@ +/** + * Licensed to the Minutemen Group under one or more contributor license + * agreements. See the COPYRIGHT file distributed with this work for + * additional information regarding copyright ownership. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package silhouette.akka.http.client + +import akka.http.scaladsl.model.HttpResponse +import akka.http.scaladsl.model.headers.{ HttpCookie, RawHeader, `Set-Cookie` } +import org.specs2.mutable.Specification +import org.specs2.specification.Scope +import silhouette.akka.http.AkkaHttpResponsePipeline +import silhouette.akka.http.session.Session +import silhouette.http.Cookie + +/** + * Test case for the [[AkkaHttpResponse]] class. + */ +class AkkaHttpResponseSpec extends Specification { + + "The `headers` method" should { + "return all headers" in new Context { + responsePipeline.headers.map(_._2.sorted) must be equalTo headers.map(_._2.sorted) + } + } + + "The `header` method" should { + "return the list of header values" in new Context { + responsePipeline.header("TEST1") must contain(exactly("value1", "value2")) + } + + "return an empty list if no header with the given name was found" in new Context { + responsePipeline.header("TEST3") must beEmpty + } + } + + "The `withHeaders` method" should { + "append a new header" in new Context { + val r = responsePipeline.withHeaders("TEST3" -> "value1") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1")) + } + + "append multiple headers" in new Context { + val r = responsePipeline.withHeaders("TEST3" -> "value1", "TEST4" -> "value1") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1")) + r.header("TEST4") must contain(exactly("value1")) + } + + "append multiple headers with the same name" in new Context { + val r = responsePipeline.withHeaders("TEST3" -> "value1", "TEST3" -> "value2") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value1")) + r.header("TEST3") must contain(exactly("value1", "value2")) + } + + "override an existing header" in new Context { + val r = responsePipeline.withHeaders("TEST2" -> "value2", "TEST2" -> "value3") + r.header("TEST1") must contain(exactly("value1", "value2")) + r.header("TEST2") must contain(exactly("value2", "value3")) + } + + "override multiple existing headers" in new Context { + val r = responsePipeline.withHeaders("TEST1" -> "value3", "TEST2" -> "value2") + r.header("TEST1") must contain(exactly("value3")) + r.header("TEST2") must contain(exactly("value2")) + } + } + + "The `cookies` method" should { + "return all cookies" in new Context { + responsePipeline.cookies must be equalTo cookies + } + } + + "The `cookie` method" should { + "return some cookie for the given name" in new Context { + responsePipeline.cookie("test1") must beSome(Cookie("test1", "value1")) + } + + "return None if no cookie with the given name was found" in new Context { + responsePipeline.cookie("test3") must beNone + } + } + + "The `withCookies` method" should { + "append a new cookie" in new Context { + val r = responsePipeline.withCookies(Cookie("test3", "value3")) + r.cookie("test1") must beSome(Cookie("test1", "value1")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + r.cookie("test3") must beSome(Cookie("test3", "value3")) + } + + "override an existing cookie" in new Context { + val r = responsePipeline.withCookies(Cookie("test1", "value3")) + r.cookie("test1") must beSome(Cookie("test1", "value3")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + } + + "use the last cookie if multiple cookies with the same name are given" in new Context { + val r = responsePipeline.withCookies(Cookie("test1", "value3"), Cookie("test1", "value4")) + r.cookie("test1") must beSome(Cookie("test1", "value4")) + r.cookie("test2") must beSome(Cookie("test2", "value2")) + } + } + + "The `session` method" should { + "return all session data" in new Context { + responsePipeline.session must be equalTo session + } + } + + "The `withSession` method" should { + "append new session data" in new Context { + responsePipeline.withSession("test3" -> "value3").session must be equalTo Map( + "test1" -> "value1", + "test2" -> "value2", + "test3" -> "value3" + ) + } + + "override existing session data" in new Context { + responsePipeline.withSession("test1" -> "value3").session must be equalTo Map( + "test1" -> "value3", + "test2" -> "value2" + ) + } + + "use the last session data if multiple session data with the same name are given" in new Context { + responsePipeline.withSession("test1" -> "value3", "test1" -> "value4").session must be equalTo Map( + "test1" -> "value4", + "test2" -> "value2" + ) + } + } + + "The `withSession` method" should { + "remove session data" in new Context { + responsePipeline.withoutSession("test1").session must be equalTo Map( + "test2" -> "value2" + ) + } + + "remove multiple keys" in new Context { + responsePipeline.withoutSession("test1", "test2").session must beEmpty + } + } + + "The `unbox` method" should { + "return the handled response" in new Context { + responsePipeline.unbox must be equalTo response + } + } + + "The touch method" should { + "touch a response" in new Context { + responsePipeline.touch.touched must beTrue + } + } + + /** + * The context. + */ + trait Context extends Scope { + + val sessionName = "session" + + val headers = Map( + "TEST1" -> Seq("value1", "value2"), + "TEST2" -> Seq("value1") + ) + val session = Map( + "test1" -> "value1", + "test2" -> "value2" + ) + val cookies = Seq( + Cookie("test1", "value1"), + Cookie("test2", "value2"), + Session.asCookie(Session(sessionName, session)) + ) + + val akkaHeaders = headers.flatMap(p => p._2.map(v => RawHeader(p._1, v))).toList + val akkaCookie = cookies.map(c => `Set-Cookie`(HttpCookie(c.name, c.value))) + val response = HttpResponse( + headers = akkaHeaders ++ akkaCookie + ) + + /** + * A response pipeline which handles a response. + */ + val responsePipeline = AkkaHttpResponsePipeline(response, sessionName) + } +} + diff --git a/silhouette-akka-http/src/main/scala/silhouette/akka/http/session/Session.scala b/silhouette-akka-http/src/main/scala/silhouette/akka/http/session/Session.scala index 5d1d9b5..8faa57c 100644 --- a/silhouette-akka-http/src/main/scala/silhouette/akka/http/session/Session.scala +++ b/silhouette-akka-http/src/main/scala/silhouette/akka/http/session/Session.scala @@ -64,7 +64,7 @@ object Session { data.map(p => s"${urlEncode(p._1)}=${urlEncode(p._2)}").mkString("&") /** - * Deserializes the URL encoded representation of the session data. + * Deserialize the URL encoded representation of the session data. * * @param data An URL encoded string representation of the session data. * @return The session data.